mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
common: make sphinx code ignorant of payload format.
Now "raw_payload" is always the complete string (including realm or length bytes at the front). This has several effects: 1. We can receive an decrypt an onion which is grossly malformed. 2. We can still hand this to the htlc_accepted hook. 3. We then fail it unless the htlc_accepted accepts it manually. 4. The createonion API now takes the raw payload, and does not know anything about "style". The only caveat is that the sphinx code needs to know the payload length: we have a call for that, which simply tells it to copy the entire onion (and treat us as the final node) if it's invalid. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
bb538a1862
commit
f7ebbb2ec5
@ -61,6 +61,7 @@ CHANNELD_COMMON_OBJS := \
|
||||
common/memleak.o \
|
||||
common/msg_queue.o \
|
||||
common/node_id.o \
|
||||
common/onion.o \
|
||||
common/peer_billboard.o \
|
||||
common/peer_failed.o \
|
||||
common/per_peer_state.o \
|
||||
|
@ -39,6 +39,7 @@ COMMON_SRC_NOGEN := \
|
||||
common/memleak.c \
|
||||
common/msg_queue.c \
|
||||
common/node_id.c \
|
||||
common/onion.c \
|
||||
common/param.c \
|
||||
common/per_peer_state.c \
|
||||
common/peer_billboard.c \
|
||||
|
@ -261,7 +261,7 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
|
||||
const char *buffer, const jsmntok_t *tok,
|
||||
struct sphinx_hop **hops)
|
||||
{
|
||||
const jsmntok_t *hop, *payloadtok, *styletok, *pubkeytok;
|
||||
const jsmntok_t *hop, *payloadtok, *pubkeytok;
|
||||
struct sphinx_hop h;
|
||||
size_t i;
|
||||
if (tok->type != JSMN_ARRAY) {
|
||||
@ -274,9 +274,7 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
|
||||
*hops = tal_arr(cmd, struct sphinx_hop, 0);
|
||||
|
||||
json_for_each_arr(i, hop, tok) {
|
||||
|
||||
payloadtok = json_get_member(buffer, hop, "payload");
|
||||
styletok = json_get_member(buffer, hop, "style");
|
||||
pubkeytok = json_get_member(buffer, hop, "pubkey");
|
||||
|
||||
if (!pubkeytok)
|
||||
@ -287,7 +285,7 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Hop %zu does not have a payload", i);
|
||||
|
||||
h.payload = json_tok_bin_from_hex(*hops, buffer, payloadtok);
|
||||
h.raw_payload = json_tok_bin_from_hex(*hops, buffer, payloadtok);
|
||||
if (!json_to_pubkey(buffer, pubkeytok, &h.pubkey))
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
@ -295,25 +293,13 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
|
||||
pubkeytok->end - pubkeytok->start,
|
||||
buffer + pubkeytok->start);
|
||||
|
||||
if (!h.payload)
|
||||
if (!h.raw_payload)
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"'payload' should be a hex encoded binary, not '%.*s'",
|
||||
pubkeytok->end - pubkeytok->start,
|
||||
buffer + pubkeytok->start);
|
||||
|
||||
if (!styletok || json_tok_streq(buffer, styletok, "tlv")) {
|
||||
h.type = SPHINX_TLV_PAYLOAD;
|
||||
} else if (json_tok_streq(buffer, styletok, "legacy")) {
|
||||
h.type = SPHINX_V0_PAYLOAD;
|
||||
} else {
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Unknown payload type for hop %zu: '%.*s'", i,
|
||||
pubkeytok->end - pubkeytok->start,
|
||||
buffer + pubkeytok->start);
|
||||
}
|
||||
|
||||
tal_arr_expand(hops, h);
|
||||
}
|
||||
|
||||
|
308
common/onion.c
Normal file
308
common/onion.c
Normal file
@ -0,0 +1,308 @@
|
||||
#include "common/onion.h"
|
||||
#include <assert.h>
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <common/sphinx.h>
|
||||
#include <wire/gen_onion_wire.h>
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* ## Legacy `hop_data` payload format
|
||||
*
|
||||
* The `hop_data` format is identified by a single `0x00`-byte length,
|
||||
* for backward compatibility. Its payload is defined as:
|
||||
*
|
||||
* 1. type: `hop_data` (for `realm` 0)
|
||||
* 2. data:
|
||||
* * [`short_channel_id`:`short_channel_id`]
|
||||
* * [`u64`:`amt_to_forward`]
|
||||
* * [`u32`:`outgoing_cltv_value`]
|
||||
* * [`12*byte`:`padding`]
|
||||
*/
|
||||
static u8 *make_v0_hop(const tal_t *ctx,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward, u32 outgoing_cltv)
|
||||
{
|
||||
const u8 padding[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
/* Prepend 0 byte for realm */
|
||||
u8 *buf = tal_arrz(ctx, u8, 1);
|
||||
towire_short_channel_id(&buf, scid);
|
||||
towire_u64(&buf, forward.millisatoshis); /* Raw: low-level serializer */
|
||||
towire_u32(&buf, outgoing_cltv);
|
||||
towire(&buf, padding, ARRAY_SIZE(padding));
|
||||
assert(tal_bytelen(buf) == 1 + 32);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static u8 *make_tlv_hop(const tal_t *ctx,
|
||||
const struct tlv_tlv_payload *tlv)
|
||||
{
|
||||
/* We can't have over 64k anyway */
|
||||
u8 *tlvs = tal_arr(ctx, u8, 3);
|
||||
|
||||
towire_tlv_payload(&tlvs, tlv);
|
||||
|
||||
switch (bigsize_put(tlvs, tal_bytelen(tlvs) - 3)) {
|
||||
case 1:
|
||||
/* Move over two unused bytes */
|
||||
memmove(tlvs + 1, tlvs + 3, tal_bytelen(tlvs) - 3);
|
||||
tal_resize(&tlvs, tal_bytelen(tlvs) - 2);
|
||||
return tlvs;
|
||||
case 3:
|
||||
return tlvs;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
u8 *onion_nonfinal_hop(const tal_t *ctx,
|
||||
bool use_tlv,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv)
|
||||
{
|
||||
if (use_tlv) {
|
||||
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
|
||||
struct tlv_tlv_payload_amt_to_forward tlv_amt;
|
||||
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
|
||||
struct tlv_tlv_payload_short_channel_id tlv_scid;
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
|
||||
* for every node.
|
||||
* - MUST include `short_channel_id` for every non-final node.
|
||||
*/
|
||||
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
|
||||
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
|
||||
tlv_scid.short_channel_id = *scid;
|
||||
tlv->amt_to_forward = &tlv_amt;
|
||||
tlv->outgoing_cltv_value = &tlv_cltv;
|
||||
tlv->short_channel_id = &tlv_scid;
|
||||
|
||||
return make_tlv_hop(ctx, tlv);
|
||||
} else {
|
||||
return make_v0_hop(ctx, scid, forward, outgoing_cltv);
|
||||
}
|
||||
}
|
||||
|
||||
u8 *onion_final_hop(const tal_t *ctx,
|
||||
bool use_tlv,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv,
|
||||
struct amount_msat total_msat,
|
||||
const struct secret *payment_secret)
|
||||
{
|
||||
/* These go together! */
|
||||
if (!payment_secret)
|
||||
assert(amount_msat_eq(total_msat, forward));
|
||||
|
||||
if (use_tlv) {
|
||||
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
|
||||
struct tlv_tlv_payload_amt_to_forward tlv_amt;
|
||||
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
struct tlv_tlv_payload_payment_data tlv_pdata;
|
||||
#endif
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
|
||||
* for every node.
|
||||
*...
|
||||
* - MUST NOT include `short_channel_id` for the final node.
|
||||
*/
|
||||
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
|
||||
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
|
||||
tlv->amt_to_forward = &tlv_amt;
|
||||
tlv->outgoing_cltv_value = &tlv_cltv;
|
||||
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
if (payment_secret) {
|
||||
tlv_pdata.payment_secret = *payment_secret;
|
||||
tlv_pdata.total_msat = total_msat.millisatoshis; /* Raw: TLV convert */
|
||||
tlv->payment_data = &tlv_pdata;
|
||||
}
|
||||
#else
|
||||
/* Wihtout EXPERIMENTAL_FEATURES, we can't send payment_secret */
|
||||
if (payment_secret)
|
||||
return NULL;
|
||||
#endif
|
||||
return make_tlv_hop(ctx, tlv);
|
||||
} else {
|
||||
static struct short_channel_id all_zero_scid;
|
||||
/* No payment secrets in legacy format. */
|
||||
if (payment_secret)
|
||||
return NULL;
|
||||
return make_v0_hop(ctx, &all_zero_scid, forward, outgoing_cltv);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true if valid, and fills in type. */
|
||||
static bool pull_payload_length(const u8 **cursor,
|
||||
size_t *max,
|
||||
enum onion_payload_type *type,
|
||||
size_t *len)
|
||||
{
|
||||
/* *len will incorporate bytes we read from cursor */
|
||||
const u8 *start = *cursor;
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The `length` field determines both the length and the format of the
|
||||
* `hop_payload` field; the following formats are defined:
|
||||
*/
|
||||
*len = fromwire_bigsize(cursor, max);
|
||||
if (!cursor)
|
||||
return false;
|
||||
|
||||
/* BOLT #4:
|
||||
* - Legacy `hop_data` format, identified by a single `0x00` byte for
|
||||
* length. In this case the `hop_payload_length` is defined to be 32
|
||||
* bytes.
|
||||
*/
|
||||
if (*len == 0) {
|
||||
if (type)
|
||||
*type = ONION_V0_PAYLOAD;
|
||||
assert(*cursor - start == 1);
|
||||
*len = 1 + 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !EXPERIMENTAL_FEATURES
|
||||
/* Only handle legacy format */
|
||||
return false;
|
||||
#else
|
||||
/* BOLT #4:
|
||||
* - `tlv_payload` format, identified by any length over `1`. In this
|
||||
* case the `hop_payload_length` is equal to the numeric value of
|
||||
* `length`.
|
||||
*/
|
||||
if (*len > 1) {
|
||||
/* It's still invalid if it claims to be too long! */
|
||||
if (*len > ROUTING_INFO_SIZE - HMAC_SIZE)
|
||||
return false;
|
||||
|
||||
if (type)
|
||||
*type = ONION_TLV_PAYLOAD;
|
||||
*len += (*cursor - start);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif /* EXPERIMENTAL_FEATURES */
|
||||
}
|
||||
|
||||
size_t onion_payload_length(const u8 *raw_payload, size_t len,
|
||||
bool *valid,
|
||||
enum onion_payload_type *type)
|
||||
{
|
||||
size_t max = len, payload_len;
|
||||
*valid = pull_payload_length(&raw_payload, &max, type, &payload_len);
|
||||
|
||||
/* If it's not valid, copy the entire thing. */
|
||||
if (!*valid)
|
||||
return len;
|
||||
|
||||
return payload_len;
|
||||
}
|
||||
|
||||
struct onion_payload *onion_decode(const tal_t *ctx,
|
||||
const struct route_step *rs)
|
||||
{
|
||||
struct onion_payload *p = tal(ctx, struct onion_payload);
|
||||
const u8 *cursor = rs->raw_payload;
|
||||
size_t max = tal_bytelen(cursor), len;
|
||||
struct tlv_tlv_payload *tlv;
|
||||
|
||||
if (!pull_payload_length(&cursor, &max, &p->type, &len))
|
||||
return tal_free(p);
|
||||
|
||||
switch (p->type) {
|
||||
case ONION_V0_PAYLOAD:
|
||||
p->type = ONION_V0_PAYLOAD;
|
||||
p->forward_channel = tal(p, struct short_channel_id);
|
||||
fromwire_short_channel_id(&cursor, &max, p->forward_channel);
|
||||
p->amt_to_forward = fromwire_amount_msat(&cursor, &max);
|
||||
p->outgoing_cltv = fromwire_u32(&cursor, &max);
|
||||
p->payment_secret = NULL;
|
||||
|
||||
if (rs->nextcase == ONION_FORWARD) {
|
||||
p->total_msat = NULL;
|
||||
} else {
|
||||
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #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);
|
||||
}
|
||||
|
||||
/* If they somehow got an invalid onion this far, fail. */
|
||||
if (!cursor)
|
||||
return tal_free(p);
|
||||
return p;
|
||||
|
||||
case ONION_TLV_PAYLOAD:
|
||||
tlv = tlv_tlv_payload_new(p);
|
||||
if (!fromwire_tlv_payload(&cursor, &max, tlv))
|
||||
return tal_free(p);
|
||||
if (!tlv_payload_is_valid(tlv, NULL))
|
||||
return tal_free(p);
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The reader:
|
||||
* - MUST return an error if `amt_to_forward` or
|
||||
* `outgoing_cltv_value` are not present.
|
||||
*/
|
||||
if (!tlv->amt_to_forward || !tlv->outgoing_cltv_value)
|
||||
return tal_free(p);
|
||||
|
||||
amount_msat_from_u64(&p->amt_to_forward,
|
||||
tlv->amt_to_forward->amt_to_forward);
|
||||
p->outgoing_cltv = tlv->outgoing_cltv_value->outgoing_cltv_value;
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
*...
|
||||
* - MUST include `short_channel_id` for every non-final node.
|
||||
*/
|
||||
if (rs->nextcase == ONION_FORWARD) {
|
||||
if (!tlv->short_channel_id)
|
||||
return tal_free(p);
|
||||
p->forward_channel = tal(p, struct short_channel_id);
|
||||
*p->forward_channel
|
||||
= tlv->short_channel_id->short_channel_id;
|
||||
p->total_msat = NULL;
|
||||
} else {
|
||||
p->forward_channel = NULL;
|
||||
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #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);
|
||||
}
|
||||
|
||||
p->payment_secret = NULL;
|
||||
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
if (tlv->payment_data) {
|
||||
p->payment_secret = tal_dup(p, struct secret,
|
||||
&tlv->payment_data->payment_secret);
|
||||
tal_free(p->total_msat);
|
||||
p->total_msat = tal(p, struct amount_msat);
|
||||
p->total_msat->millisatoshis /* Raw: tu64 on wire */
|
||||
= tlv->payment_data->total_msat;
|
||||
}
|
||||
#endif
|
||||
tal_free(tlv);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* You said it was a valid type! */
|
||||
abort();
|
||||
}
|
64
common/onion.h
Normal file
64
common/onion.h
Normal file
@ -0,0 +1,64 @@
|
||||
#ifndef LIGHTNING_COMMON_ONION_H
|
||||
#define LIGHTNING_COMMON_ONION_H
|
||||
#include "config.h"
|
||||
#include <ccan/short_types/short_types.h>
|
||||
#include <common/amount.h>
|
||||
|
||||
struct route_step;
|
||||
|
||||
enum onion_payload_type {
|
||||
ONION_V0_PAYLOAD = 0,
|
||||
ONION_TLV_PAYLOAD = 1,
|
||||
};
|
||||
|
||||
struct onion_payload {
|
||||
enum onion_payload_type type;
|
||||
|
||||
struct amount_msat amt_to_forward;
|
||||
u32 outgoing_cltv;
|
||||
struct amount_msat *total_msat;
|
||||
struct short_channel_id *forward_channel;
|
||||
struct secret *payment_secret;
|
||||
};
|
||||
|
||||
u8 *onion_nonfinal_hop(const tal_t *ctx,
|
||||
bool use_tlv,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv);
|
||||
|
||||
/* Note that this can fail if we supply payment_secret and !use_tlv! */
|
||||
u8 *onion_final_hop(const tal_t *ctx,
|
||||
bool use_tlv,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv,
|
||||
struct amount_msat total_msat,
|
||||
const struct secret *payment_secret);
|
||||
|
||||
/**
|
||||
* onion_payload_length: measure payload length in decrypted onion.
|
||||
* @raw_payload: payload to look at.
|
||||
* @len: length of @raw_payload in bytes.
|
||||
* @valid: set to true if it is valid, false otherwise.
|
||||
* @type: if non-NULL, set to type of payload if *@valid is true.
|
||||
*
|
||||
* If @valid is set, there is room for the HMAC immediately following,
|
||||
* as the return value is <= ROUTING_INFO_SIZE - HMAC_SIZE. Otherwise,
|
||||
* the return value is @len (i.e. the entire payload).
|
||||
*/
|
||||
size_t onion_payload_length(const u8 *raw_payload, size_t len,
|
||||
bool *valid,
|
||||
enum onion_payload_type *type);
|
||||
|
||||
/**
|
||||
* onion_decode: decode payload from a decrypted onion.
|
||||
* @ctx: context to allocate onion_contents off.
|
||||
* @rs: the route_step, whose raw_payload is of at least length
|
||||
* onion_payload_length().
|
||||
*
|
||||
* If the payload is not valid, returns NULL.
|
||||
*/
|
||||
struct onion_payload *onion_decode(const tal_t *ctx,
|
||||
const struct route_step *rs);
|
||||
|
||||
#endif /* LIGHTNING_COMMON_ONION_H */
|
354
common/sphinx.c
354
common/sphinx.c
@ -5,6 +5,7 @@
|
||||
#include <ccan/crypto/sha256/sha256.h>
|
||||
#include <ccan/mem/mem.h>
|
||||
#include <common/node_id.h>
|
||||
#include <common/onion.h>
|
||||
#include <common/sphinx.h>
|
||||
#include <common/utils.h>
|
||||
|
||||
@ -79,29 +80,7 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx,
|
||||
|
||||
static size_t sphinx_hop_size(const struct sphinx_hop *hop)
|
||||
{
|
||||
size_t size = tal_bytelen(hop->payload), vsize;
|
||||
|
||||
/* There is no point really in trying to serialize something that is
|
||||
* larger than the maximum length we can fit into the payload region
|
||||
* anyway. 3 here is the maximum bigsize size that we allow. */
|
||||
assert(size < ROUTING_INFO_SIZE - 3 - HMAC_SIZE);
|
||||
|
||||
/* Backwards compatibility: realm 0 is the legacy hop_data format and
|
||||
* always has 65 bytes in size */
|
||||
if (hop->type == SPHINX_V0_PAYLOAD)
|
||||
return 65;
|
||||
|
||||
/* Since this uses the bigsize serialization format for variable
|
||||
* length integer encodings we need to allocate enough space for
|
||||
* it. Values >= 0xfd are used to signal multi-byte serializations. */
|
||||
if (size < 0xFD)
|
||||
vsize = 1;
|
||||
else
|
||||
vsize = 3;
|
||||
|
||||
/* The hop must accomodate the hop_payload, as well as the bigsize
|
||||
* describing the length and HMAC. */
|
||||
return vsize + size + HMAC_SIZE;
|
||||
return tal_bytelen(hop->raw_payload) + HMAC_SIZE;
|
||||
}
|
||||
|
||||
static size_t sphinx_path_payloads_size(const struct sphinx_path *path)
|
||||
@ -112,131 +91,16 @@ static size_t sphinx_path_payloads_size(const struct sphinx_path *path)
|
||||
return size;
|
||||
}
|
||||
|
||||
void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey,
|
||||
enum sphinx_payload_type type, const u8 *payload)
|
||||
void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey,
|
||||
const u8 *payload TAKES)
|
||||
{
|
||||
struct sphinx_hop sp;
|
||||
sp.payload = payload;
|
||||
sp.type = type;
|
||||
sp.raw_payload = tal_dup_arr(path, u8, payload, tal_count(payload), 0);
|
||||
sp.pubkey = *pubkey;
|
||||
tal_arr_expand(&path->hops, sp);
|
||||
assert(sphinx_path_payloads_size(path) <= ROUTING_INFO_SIZE);
|
||||
}
|
||||
|
||||
static void sphinx_add_v0_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward, u32 outgoing_cltv)
|
||||
{
|
||||
const u8 padding[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
u8 *buf = tal_arr(path, u8, 0);
|
||||
towire_short_channel_id(&buf, scid);
|
||||
towire_u64(&buf, forward.millisatoshis); /* Raw: low-level serializer */
|
||||
towire_u32(&buf, outgoing_cltv);
|
||||
towire(&buf, padding, ARRAY_SIZE(padding));
|
||||
assert(tal_bytelen(buf) == 32);
|
||||
sphinx_add_raw_hop(path, pubkey, SPHINX_V0_PAYLOAD, buf);
|
||||
}
|
||||
|
||||
static void sphinx_add_tlv_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
const struct tlv_tlv_payload *tlv)
|
||||
{
|
||||
u8 *tlvs = tal_arr(path, u8, 0);
|
||||
towire_tlv_payload(&tlvs, tlv);
|
||||
sphinx_add_raw_hop(path, pubkey, SPHINX_TLV_PAYLOAD, tlvs);
|
||||
}
|
||||
|
||||
void sphinx_add_nonfinal_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
bool use_tlv,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv)
|
||||
{
|
||||
if (use_tlv) {
|
||||
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
|
||||
struct tlv_tlv_payload_amt_to_forward tlv_amt;
|
||||
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
|
||||
struct tlv_tlv_payload_short_channel_id tlv_scid;
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
|
||||
* for every node.
|
||||
* - MUST include `short_channel_id` for every non-final node.
|
||||
*/
|
||||
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
|
||||
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
|
||||
tlv_scid.short_channel_id = *scid;
|
||||
tlv->amt_to_forward = &tlv_amt;
|
||||
tlv->outgoing_cltv_value = &tlv_cltv;
|
||||
tlv->short_channel_id = &tlv_scid;
|
||||
|
||||
sphinx_add_tlv_hop(path, pubkey, tlv);
|
||||
} else {
|
||||
sphinx_add_v0_hop(path, pubkey, scid, forward, outgoing_cltv);
|
||||
}
|
||||
}
|
||||
|
||||
bool sphinx_add_final_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
bool use_tlv,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv,
|
||||
struct amount_msat total_msat,
|
||||
const struct secret *payment_secret)
|
||||
{
|
||||
/* These go together! */
|
||||
if (!payment_secret)
|
||||
assert(amount_msat_eq(total_msat, forward));
|
||||
|
||||
if (use_tlv) {
|
||||
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
|
||||
struct tlv_tlv_payload_amt_to_forward tlv_amt;
|
||||
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
struct tlv_tlv_payload_payment_data tlv_pdata;
|
||||
#endif
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
|
||||
* for every node.
|
||||
*...
|
||||
* - MUST NOT include `short_channel_id` for the final node.
|
||||
*/
|
||||
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
|
||||
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
|
||||
tlv->amt_to_forward = &tlv_amt;
|
||||
tlv->outgoing_cltv_value = &tlv_cltv;
|
||||
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
if (payment_secret) {
|
||||
tlv_pdata.payment_secret = *payment_secret;
|
||||
tlv_pdata.total_msat = total_msat.millisatoshis; /* Raw: TLV convert */
|
||||
tlv->payment_data = &tlv_pdata;
|
||||
}
|
||||
#else
|
||||
/* Wihtout EXPERIMENTAL_FEATURES, we can't send payment_secret */
|
||||
if (payment_secret)
|
||||
return false;
|
||||
#endif
|
||||
sphinx_add_tlv_hop(path, pubkey, tlv);
|
||||
} else {
|
||||
static struct short_channel_id all_zero_scid;
|
||||
/* No payment secrets in legacy format. */
|
||||
if (payment_secret)
|
||||
return false;
|
||||
sphinx_add_v0_hop(path, pubkey, &all_zero_scid,
|
||||
forward, outgoing_cltv);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Small helper to append data to a buffer and update the position
|
||||
* into the buffer
|
||||
*/
|
||||
@ -514,100 +378,10 @@ static struct hop_params *generate_hop_params(
|
||||
return params;
|
||||
}
|
||||
|
||||
static void deserialize_hop_data(struct hop_data_legacy *data, const u8 *src)
|
||||
static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop)
|
||||
{
|
||||
const u8 *cursor = src;
|
||||
size_t max = FRAME_SIZE;
|
||||
data->realm = fromwire_u8(&cursor, &max);
|
||||
fromwire_short_channel_id(&cursor, &max, &data->channel_id);
|
||||
data->amt_forward = fromwire_amount_msat(&cursor, &max);
|
||||
data->outgoing_cltv = fromwire_u32(&cursor, &max);
|
||||
}
|
||||
|
||||
static bool sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop)
|
||||
{
|
||||
size_t raw_size = tal_bytelen(hop->payload);
|
||||
size_t hop_size = sphinx_hop_size(hop);
|
||||
size_t padding_size;
|
||||
int pos = 0;
|
||||
|
||||
/* Backwards compatibility for the legacy hop_data format. */
|
||||
if (hop->type == SPHINX_V0_PAYLOAD)
|
||||
dest[pos++] = 0x00;
|
||||
else
|
||||
pos += bigsize_put(dest+pos, raw_size);
|
||||
|
||||
memcpy(dest + pos, hop->payload, raw_size);
|
||||
pos += raw_size;
|
||||
|
||||
padding_size = hop_size - pos - HMAC_SIZE;
|
||||
memset(dest + pos, 0, padding_size);
|
||||
pos += padding_size;
|
||||
|
||||
memcpy(dest + pos, hop->hmac, HMAC_SIZE);
|
||||
assert(pos + HMAC_SIZE == hop_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sphinx_parse_payload(struct route_step *step, const u8 *src)
|
||||
{
|
||||
size_t hop_size, vsize;
|
||||
bigsize_t raw_size;
|
||||
#if !EXPERIMENTAL_FEATURES
|
||||
if (src[0] != 0x00)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The `length` field determines both the length and the format of the
|
||||
* `hop_payload` field; the following formats are defined:
|
||||
*
|
||||
* - Legacy `hop_data` format, identified by a single `0x00` byte for
|
||||
* length. In this case the `hop_payload_length` is defined to be 32
|
||||
* bytes.
|
||||
*
|
||||
* - `tlv_payload` format, identified by any length over `1`. In this
|
||||
* case the `hop_payload_length` is equal to the numeric value of
|
||||
* `length`.
|
||||
*/
|
||||
if (src[0] == 0x00) {
|
||||
vsize = 1;
|
||||
raw_size = 32;
|
||||
hop_size = FRAME_SIZE;
|
||||
step->type = SPHINX_V0_PAYLOAD;
|
||||
} else if (src[0] > 1) {
|
||||
vsize = bigsize_get(src, 3, &raw_size);
|
||||
hop_size = raw_size + vsize + HMAC_SIZE;
|
||||
step->type = SPHINX_TLV_PAYLOAD;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy common pieces over */
|
||||
step->raw_payload = tal_dup_arr(step, u8, src, raw_size + vsize, 0);
|
||||
memcpy(step->next->mac, src + hop_size - HMAC_SIZE, HMAC_SIZE);
|
||||
|
||||
/* And now try to parse whatever the payload contains so we can use it
|
||||
* later. */
|
||||
if (step->type == SPHINX_V0_PAYLOAD)
|
||||
deserialize_hop_data(&step->payload.v0, src);
|
||||
else if (step->type == SPHINX_TLV_PAYLOAD) {
|
||||
const u8 *tlv = step->raw_payload;
|
||||
size_t max = tal_bytelen(tlv);
|
||||
step->payload.tlv = tlv_tlv_payload_new(step);
|
||||
|
||||
/* The raw payload includes the length / realm prefix, Consume
|
||||
* the length off of the payload, so the decoding can strat
|
||||
* correctly. */
|
||||
fromwire_varint(&tlv, &max);
|
||||
|
||||
if (!fromwire_tlv_payload(&tlv, &max, step->payload.tlv)) {
|
||||
/* FIXME: record offset of violation for error! */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
memcpy(dest, hop->raw_payload, tal_bytelen(hop->raw_payload));
|
||||
memcpy(dest + tal_bytelen(hop->raw_payload), hop->hmac, HMAC_SIZE);
|
||||
}
|
||||
|
||||
struct onionpacket *create_onionpacket(
|
||||
@ -663,11 +437,7 @@ struct onionpacket *create_onionpacket(
|
||||
size_t shiftSize = sphinx_hop_size(&sp->hops[i]);
|
||||
memmove(packet->routinginfo + shiftSize, packet->routinginfo,
|
||||
ROUTING_INFO_SIZE-shiftSize);
|
||||
if (!sphinx_write_frame(packet->routinginfo, &sp->hops[i])) {
|
||||
tal_free(packet);
|
||||
tal_free(secrets);
|
||||
return NULL;
|
||||
}
|
||||
sphinx_write_frame(packet->routinginfo, &sp->hops[i]);
|
||||
xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE);
|
||||
|
||||
if (i == num_hops - 1) {
|
||||
@ -688,74 +458,6 @@ struct onionpacket *create_onionpacket(
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to extract fields from the legacy or tlv payload into the top-level
|
||||
* struct.
|
||||
*/
|
||||
static void route_step_decode(struct route_step *rs)
|
||||
{
|
||||
switch (rs->type) {
|
||||
case SPHINX_V0_PAYLOAD:
|
||||
rs->amt_to_forward = &rs->payload.v0.amt_forward;
|
||||
rs->outgoing_cltv = &rs->payload.v0.outgoing_cltv;
|
||||
rs->payment_secret = NULL;
|
||||
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #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. */
|
||||
rs->total_msat = rs->amt_to_forward;
|
||||
if (rs->nextcase == ONION_FORWARD) {
|
||||
rs->forward_channel = &rs->payload.v0.channel_id;
|
||||
} else {
|
||||
rs->forward_channel = NULL;
|
||||
}
|
||||
break;
|
||||
case SPHINX_TLV_PAYLOAD:
|
||||
if (rs->payload.tlv->amt_to_forward) {
|
||||
rs->amt_to_forward = tal(rs, struct amount_msat);
|
||||
amount_msat_from_u64(
|
||||
rs->amt_to_forward,
|
||||
rs->payload.tlv->amt_to_forward->amt_to_forward);
|
||||
} else {
|
||||
rs->amt_to_forward = NULL;
|
||||
}
|
||||
|
||||
if (rs->payload.tlv->outgoing_cltv_value) {
|
||||
rs->outgoing_cltv =
|
||||
&rs->payload.tlv->outgoing_cltv_value
|
||||
->outgoing_cltv_value;
|
||||
} else {
|
||||
rs->outgoing_cltv = NULL;
|
||||
}
|
||||
|
||||
if (rs->payload.tlv->short_channel_id)
|
||||
rs->forward_channel = &rs->payload.tlv->short_channel_id
|
||||
->short_channel_id;
|
||||
else
|
||||
rs->forward_channel = NULL;
|
||||
|
||||
rs->payment_secret = NULL;
|
||||
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #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. */
|
||||
rs->total_msat = rs->amt_to_forward;
|
||||
|
||||
#if EXPERIMENTAL_FEATURES
|
||||
if (rs->payload.tlv->payment_data) {
|
||||
rs->payment_secret
|
||||
= &rs->payload.tlv->payment_data->payment_secret;
|
||||
rs->total_msat = tal(rs, struct amount_msat);
|
||||
rs->total_msat->millisatoshis /* Raw: tu64 on wire */
|
||||
= rs->payload.tlv->payment_data->total_msat;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case SPHINX_RAW_PAYLOAD:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given an onionpacket msg extract the information for the current
|
||||
* node and unwrap the remainder so that the node can forward it.
|
||||
@ -774,8 +476,9 @@ struct route_step *process_onionpacket(
|
||||
u8 blind[BLINDING_FACTOR_SIZE];
|
||||
u8 stream[NUM_STREAM_BYTES];
|
||||
u8 paddedheader[2*ROUTING_INFO_SIZE];
|
||||
size_t vsize;
|
||||
size_t payload_size;
|
||||
bigsize_t shift_size;
|
||||
bool valid;
|
||||
|
||||
step->next = talz(step, struct onionpacket);
|
||||
step->next->version = msg->version;
|
||||
@ -799,28 +502,29 @@ struct route_step *process_onionpacket(
|
||||
if (!blind_group_element(&step->next->ephemeralkey, &msg->ephemeralkey, blind))
|
||||
return tal_free(step);
|
||||
|
||||
if (!sphinx_parse_payload(step, paddedheader))
|
||||
payload_size = onion_payload_length(paddedheader, ROUTING_INFO_SIZE,
|
||||
&valid, NULL);
|
||||
#if !EXPERIMENTAL_FEATURES
|
||||
/* We don't even attempt to handle non-legacy or malformed payloads */
|
||||
if (!valid)
|
||||
return tal_free(step);
|
||||
#endif
|
||||
|
||||
/* Extract how many bytes we need to shift away */
|
||||
if (paddedheader[0] == 0x00) {
|
||||
shift_size = FRAME_SIZE;
|
||||
/* Can't decode? Treat it as terminal. */
|
||||
if (!valid) {
|
||||
shift_size = payload_size;
|
||||
memset(step->next->mac, 0, sizeof(step->next->mac));
|
||||
} else {
|
||||
/* In addition to the raw payload we need to also shift the
|
||||
* length encoding itself and the HMAC away. */
|
||||
vsize = bigsize_get(paddedheader, 3, &shift_size);
|
||||
shift_size += vsize + HMAC_SIZE;
|
||||
|
||||
/* If we get an unreasonable shift size we must return an error. */
|
||||
if (shift_size >= ROUTING_INFO_SIZE)
|
||||
return tal_free(step);
|
||||
assert(payload_size <= ROUTING_INFO_SIZE - HMAC_SIZE);
|
||||
/* Copy hmac */
|
||||
shift_size = payload_size + HMAC_SIZE;
|
||||
memcpy(step->next->mac, paddedheader + payload_size, HMAC_SIZE);
|
||||
}
|
||||
|
||||
/* Copy the hmac from the last HMAC_SIZE bytes */
|
||||
memcpy(&step->next->mac, paddedheader + shift_size - HMAC_SIZE, HMAC_SIZE);
|
||||
step->raw_payload = tal_dup_arr(step, u8, paddedheader, payload_size, 0);
|
||||
|
||||
/* Left shift the current payload out and make the remainder the new onion */
|
||||
memcpy(&step->next->routinginfo, paddedheader + shift_size, ROUTING_INFO_SIZE);
|
||||
memcpy(&step->next->routinginfo, paddedheader + shift_size,
|
||||
ROUTING_INFO_SIZE);
|
||||
|
||||
if (memeqzero(step->next->mac, sizeof(step->next->mac))) {
|
||||
step->nextcase = ONION_END;
|
||||
@ -828,8 +532,6 @@ struct route_step *process_onionpacket(
|
||||
step->nextcase = ONION_FORWARD;
|
||||
}
|
||||
|
||||
route_step_decode(step);
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
|
@ -68,39 +68,20 @@ struct hop_data_legacy {
|
||||
u32 outgoing_cltv;
|
||||
};
|
||||
|
||||
enum sphinx_payload_type {
|
||||
SPHINX_V0_PAYLOAD = 0,
|
||||
SPHINX_TLV_PAYLOAD = 1,
|
||||
SPHINX_RAW_PAYLOAD = 255,
|
||||
};
|
||||
|
||||
/*
|
||||
* All the necessary information to generate a valid onion for this hop on a
|
||||
* sphinx path. The payload is preserialized in order since the onion
|
||||
* generation is payload agnostic. */
|
||||
struct sphinx_hop {
|
||||
struct pubkey pubkey;
|
||||
enum sphinx_payload_type type;
|
||||
const u8 *payload;
|
||||
const u8 *raw_payload;
|
||||
u8 hmac[HMAC_SIZE];
|
||||
};
|
||||
|
||||
struct route_step {
|
||||
enum route_next_case nextcase;
|
||||
struct onionpacket *next;
|
||||
enum sphinx_payload_type type;
|
||||
union {
|
||||
struct hop_data_legacy v0;
|
||||
struct tlv_tlv_payload *tlv;
|
||||
} payload;
|
||||
u8 *raw_payload;
|
||||
|
||||
/* Quick access for internal use. */
|
||||
struct amount_msat *amt_to_forward;
|
||||
u32 *outgoing_cltv;
|
||||
struct short_channel_id *forward_channel;
|
||||
struct secret *payment_secret;
|
||||
struct amount_msat *total_msat;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -237,30 +218,9 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx,
|
||||
const struct secret *session_key);
|
||||
|
||||
/**
|
||||
* Add a raw payload hop to the path.
|
||||
* Add a payload hop to the path.
|
||||
*/
|
||||
void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey,
|
||||
enum sphinx_payload_type type, const u8 *payload);
|
||||
|
||||
/**
|
||||
* Add a non-final hop to the path.
|
||||
*/
|
||||
void sphinx_add_nonfinal_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
bool use_tlv,
|
||||
const struct short_channel_id *scid,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv);
|
||||
|
||||
/**
|
||||
* Add a final hop to the path.
|
||||
*/
|
||||
bool sphinx_add_final_hop(struct sphinx_path *path,
|
||||
const struct pubkey *pubkey,
|
||||
bool use_tlv,
|
||||
struct amount_msat forward,
|
||||
u32 outgoing_cltv,
|
||||
struct amount_msat total_msat,
|
||||
const struct secret *payment_secret);
|
||||
void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey,
|
||||
const u8 *payload TAKES);
|
||||
|
||||
#endif /* LIGHTNING_COMMON_SPHINX_H */
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "../onion.c"
|
||||
#include "../sphinx.c"
|
||||
#include <secp256k1.h>
|
||||
#include <ccan/opt/opt.h>
|
||||
|
@ -20,6 +20,7 @@ DEVTOOLS_COMMON_OBJS := \
|
||||
common/hash_u5.o \
|
||||
common/memleak.o \
|
||||
common/node_id.o \
|
||||
common/onion.o \
|
||||
common/per_peer_state.o \
|
||||
common/pseudorand.o \
|
||||
common/json.o \
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <common/amount.h>
|
||||
#include <common/json.h>
|
||||
#include <common/json_helpers.h>
|
||||
#include <common/onion.h>
|
||||
#include <common/sphinx.h>
|
||||
#include <common/utils.h>
|
||||
#include <common/version.h>
|
||||
@ -65,8 +66,7 @@ static void do_generate(int argc, char **argv,
|
||||
|
||||
if (!data)
|
||||
errx(1, "bad hex after / in %s", argv[1 + i]);
|
||||
sphinx_add_raw_hop(sp, &path[i], SPHINX_RAW_PAYLOAD,
|
||||
data);
|
||||
sphinx_add_hop(sp, &path[i], data);
|
||||
} else {
|
||||
struct short_channel_id scid;
|
||||
struct amount_msat amt;
|
||||
@ -76,13 +76,17 @@ static void do_generate(int argc, char **argv,
|
||||
memset(&scid, i, sizeof(scid));
|
||||
amt.millisatoshis = i; /* Raw: test code */
|
||||
if (i == num_hops - 1)
|
||||
sphinx_add_final_hop(sp, &path[i],
|
||||
use_tlv,
|
||||
amt, i, amt, NULL);
|
||||
sphinx_add_hop(sp, &path[i],
|
||||
take(onion_final_hop(NULL,
|
||||
use_tlv,
|
||||
amt, i, amt,
|
||||
NULL)));
|
||||
else
|
||||
sphinx_add_nonfinal_hop(sp, &path[i],
|
||||
use_tlv,
|
||||
&scid, amt, i);
|
||||
sphinx_add_hop(sp, &path[i],
|
||||
take(onion_nonfinal_hop(NULL,
|
||||
use_tlv,
|
||||
&scid,
|
||||
amt, i)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,7 +187,6 @@ static void runtest(const char *filename)
|
||||
struct pubkey pubkey;
|
||||
struct sphinx_path *path;
|
||||
size_t i;
|
||||
enum sphinx_payload_type type;
|
||||
struct onionpacket *res;
|
||||
struct route_step *step;
|
||||
char *hexprivkey;
|
||||
@ -207,17 +210,26 @@ static void runtest(const char *filename)
|
||||
/* Unpack the hops and build up the path */
|
||||
hopstok = json_get_member(buffer, gentok, "hops");
|
||||
json_for_each_arr(i, hop, hopstok) {
|
||||
u8 *full;
|
||||
size_t prepended;
|
||||
|
||||
payloadtok = json_get_member(buffer, hop, "payload");
|
||||
typetok = json_get_member(buffer, hop, "type");
|
||||
pubkeytok = json_get_member(buffer, hop, "pubkey");
|
||||
payload = json_tok_bin_from_hex(ctx, buffer, payloadtok);
|
||||
json_to_pubkey(buffer, pubkeytok, &pubkey);
|
||||
if (!typetok || json_tok_streq(buffer, typetok, "legacy")) {
|
||||
type = SPHINX_V0_PAYLOAD;
|
||||
/* Legacy has a single 0 prepended as "realm" byte */
|
||||
full = tal_arrz(ctx, u8, 1);
|
||||
} else {
|
||||
type = SPHINX_RAW_PAYLOAD;
|
||||
/* TLV has length prepended */
|
||||
full = tal_arr(ctx, u8, 0);
|
||||
towire_bigsize(&full, tal_bytelen(payload));
|
||||
}
|
||||
sphinx_add_raw_hop(path, &pubkey, type, payload);
|
||||
prepended = tal_bytelen(full);
|
||||
tal_resize(&full, prepended + tal_bytelen(payload));
|
||||
memcpy(full + prepended, payload, tal_bytelen(payload));
|
||||
sphinx_add_hop(path, &pubkey, full);
|
||||
}
|
||||
res = create_onionpacket(ctx, path, &shared_secrets);
|
||||
serialized = serialize_onionpacket(ctx, res);
|
||||
@ -242,13 +254,20 @@ static void runtest(const char *filename)
|
||||
decodetok = json_get_member(buffer, toks, "decode");
|
||||
|
||||
json_for_each_arr(i, hop, decodetok) {
|
||||
enum onion_payload_type type;
|
||||
bool valid;
|
||||
|
||||
hexprivkey = json_strdup(ctx, buffer, hop);
|
||||
printf("Processing at hop %zu\n", i);
|
||||
step = decode_with_privkey(ctx, serialized, hexprivkey, associated_data);
|
||||
serialized = serialize_onionpacket(ctx, step->next);
|
||||
if (!serialized)
|
||||
errx(1, "Error serializing message.");
|
||||
printf(" Type: %d\n", step->type);
|
||||
onion_payload_length(step->raw_payload,
|
||||
tal_bytelen(step->raw_payload),
|
||||
&valid, &type);
|
||||
assert(valid);
|
||||
printf(" Type: %d\n", type);
|
||||
printf(" Payload: %s\n", tal_hex(ctx, step->raw_payload));
|
||||
printf(" Next onion: %s\n", tal_hex(ctx, serialized));
|
||||
printf(" Next HMAC: %s\n", tal_hexstr(ctx, step->next->mac, HMAC_SIZE));
|
||||
|
8
doc/lightning-createonion.7
generated
8
doc/lightning-createonion.7
generated
@ -19,17 +19,15 @@ payload destined for that node\. The following is an example of a 3 hop onion:
|
||||
.RS
|
||||
[
|
||||
{
|
||||
"style": "legacy",
|
||||
"pubkey": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59",
|
||||
"payload": "000067000001000100000000000003e90000007b000000000000000000000000"
|
||||
"payload": "00000067000001000100000000000003e90000007b000000000000000000000000000000000000000000000000"
|
||||
}, {
|
||||
"style": "legacy",
|
||||
"pubkey": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
|
||||
"payload": "000067000003000100000000000003e800000075000000000000000000000000"
|
||||
"payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000"
|
||||
}, {
|
||||
"style": "legacy",
|
||||
"pubkey": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199",
|
||||
"payload": "000067000003000100000000000003e800000075000000000000000000000000"
|
||||
"payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
]
|
||||
.RE
|
||||
|
@ -19,17 +19,15 @@ payload destined for that node. The following is an example of a 3 hop onion:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"style": "legacy",
|
||||
"pubkey": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59",
|
||||
"payload": "000067000001000100000000000003e90000007b000000000000000000000000"
|
||||
"payload": "00000067000001000100000000000003e90000007b000000000000000000000000000000000000000000000000"
|
||||
}, {
|
||||
"style": "legacy",
|
||||
"pubkey": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
|
||||
"payload": "000067000003000100000000000003e800000075000000000000000000000000"
|
||||
"payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000"
|
||||
}, {
|
||||
"style": "legacy",
|
||||
"pubkey": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199",
|
||||
"payload": "000067000003000100000000000003e800000075000000000000000000000000"
|
||||
"payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -45,6 +45,7 @@ LIGHTNINGD_COMMON_OBJS := \
|
||||
common/memleak.o \
|
||||
common/msg_queue.o \
|
||||
common/node_id.o \
|
||||
common/onion.o \
|
||||
common/param.o \
|
||||
common/per_peer_state.o \
|
||||
common/permute_tx.o \
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <common/json_command.h>
|
||||
#include <common/json_helpers.h>
|
||||
#include <common/jsonrpc_errors.h>
|
||||
#include <common/onion.h>
|
||||
#include <common/param.h>
|
||||
#include <common/timeout.h>
|
||||
#include <gossipd/gen_gossip_wire.h>
|
||||
@ -725,6 +726,7 @@ send_payment(struct lightningd *ld,
|
||||
struct sphinx_path *path;
|
||||
struct pubkey pubkey;
|
||||
bool final_tlv, ret;
|
||||
u8 *onion;
|
||||
|
||||
/* Expiry for HTLCs is absolute. And add one to give some margin. */
|
||||
base_expiry = get_block_height(ld->topology) + 1;
|
||||
@ -739,11 +741,12 @@ send_payment(struct lightningd *ld,
|
||||
ret = pubkey_from_node_id(&pubkey, &ids[i]);
|
||||
assert(ret);
|
||||
|
||||
sphinx_add_nonfinal_hop(path, &pubkey,
|
||||
sphinx_add_hop(path, &pubkey,
|
||||
take(onion_nonfinal_hop(NULL,
|
||||
should_use_tlv(route[i].style),
|
||||
&route[i + 1].channel_id,
|
||||
route[i + 1].amount,
|
||||
base_expiry + route[i + 1].delay);
|
||||
base_expiry + route[i + 1].delay)));
|
||||
}
|
||||
|
||||
/* And finally set the final hop to the special values in
|
||||
@ -763,15 +766,17 @@ send_payment(struct lightningd *ld,
|
||||
if (!final_tlv && payment_secret)
|
||||
final_tlv = true;
|
||||
|
||||
if (!sphinx_add_final_hop(path, &pubkey,
|
||||
final_tlv,
|
||||
route[i].amount,
|
||||
base_expiry + route[i].delay,
|
||||
route[i].amount, payment_secret)) {
|
||||
onion = onion_final_hop(cmd,
|
||||
final_tlv,
|
||||
route[i].amount,
|
||||
base_expiry + route[i].delay,
|
||||
route[i].amount, payment_secret);
|
||||
if (!onion) {
|
||||
return command_fail(cmd, PAY_DESTINATION_PERM_FAIL,
|
||||
"Destination does not support"
|
||||
" payment_secret");
|
||||
}
|
||||
sphinx_add_hop(path, &pubkey, onion);
|
||||
|
||||
/* Now, do we already have a payment? */
|
||||
payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash);
|
||||
@ -1381,8 +1386,7 @@ static struct command_result *json_createonion(struct command *cmd,
|
||||
sp = sphinx_path_new_with_key(cmd, assocdata, session_key);
|
||||
|
||||
for (size_t i=0; i<tal_count(hops); i++)
|
||||
sphinx_add_raw_hop(sp, &hops[i].pubkey, hops[i].type,
|
||||
hops[i].payload);
|
||||
sphinx_add_hop(sp, &hops[i].pubkey, hops[i].raw_payload);
|
||||
|
||||
packet = create_onionpacket(cmd, sp, &shared_secrets);
|
||||
if (!packet)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <channeld/gen_channel_wire.h>
|
||||
#include <common/json_command.h>
|
||||
#include <common/jsonrpc_errors.h>
|
||||
#include <common/onion.h>
|
||||
#include <common/overflows.h>
|
||||
#include <common/param.h>
|
||||
#include <common/sphinx.h>
|
||||
@ -655,6 +656,8 @@ static void channel_resolve_reply(struct subd *gossip, const u8 *msg,
|
||||
*/
|
||||
struct htlc_accepted_hook_payload {
|
||||
struct route_step *route_step;
|
||||
/* NULL if it couldn't be parsed! */
|
||||
struct onion_payload *payload;
|
||||
struct htlc_in *hin;
|
||||
struct channel *channel;
|
||||
struct lightningd *ld;
|
||||
@ -746,34 +749,43 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p,
|
||||
s32 expiry = hin->cltv_expiry, blockheight = p->ld->topology->tip->height;
|
||||
json_object_start(s, "onion");
|
||||
|
||||
json_add_hex_talarr (s, "payload", rs->raw_payload);
|
||||
if (rs->type == SPHINX_V0_PAYLOAD) {
|
||||
if (deprecated_apis) {
|
||||
json_object_start(s, "per_hop_v0");
|
||||
json_add_string(s, "realm", "00");
|
||||
json_add_short_channel_id(s, "short_channel_id", &rs->payload.v0.channel_id);
|
||||
json_add_amount_msat_only(s, "forward_amount", rs->payload.v0.amt_forward);
|
||||
json_add_u64(s, "outgoing_cltv_value", rs->payload.v0.outgoing_cltv);
|
||||
json_object_end(s);
|
||||
}
|
||||
json_add_string(s, "type", "legacy");
|
||||
} else if (rs->type == SPHINX_TLV_PAYLOAD) {
|
||||
json_add_string(s, "type", "tlv");
|
||||
}
|
||||
json_add_hex_talarr(s, "payload", rs->raw_payload);
|
||||
if (p->payload) {
|
||||
switch (p->payload->type) {
|
||||
case ONION_V0_PAYLOAD:
|
||||
if (deprecated_apis) {
|
||||
json_object_start(s, "per_hop_v0");
|
||||
json_add_string(s, "realm", "00");
|
||||
json_add_short_channel_id(s, "short_channel_id",
|
||||
p->payload->forward_channel);
|
||||
json_add_amount_msat_only(s, "forward_amount",
|
||||
p->payload->amt_to_forward);
|
||||
json_add_u64(s, "outgoing_cltv_value",
|
||||
p->payload->outgoing_cltv);
|
||||
json_object_end(s);
|
||||
}
|
||||
json_add_string(s, "type", "legacy");
|
||||
break;
|
||||
|
||||
if (rs->forward_channel)
|
||||
json_add_short_channel_id(s, "short_channel_id",
|
||||
rs->forward_channel);
|
||||
if (rs->amt_to_forward)
|
||||
case ONION_TLV_PAYLOAD:
|
||||
json_add_string(s, "type", "tlv");
|
||||
break;
|
||||
}
|
||||
|
||||
if (p->payload->forward_channel)
|
||||
json_add_short_channel_id(s, "short_channel_id",
|
||||
p->payload->forward_channel);
|
||||
json_add_amount_msat_only(s, "forward_amount",
|
||||
*rs->amt_to_forward);
|
||||
if (rs->outgoing_cltv)
|
||||
json_add_u32(s, "outgoing_cltv_value", *rs->outgoing_cltv);
|
||||
/* These are specified together in TLV, so only print total_msat if
|
||||
* payment_secret set (ie. modern, and final hop) */
|
||||
if (rs->payment_secret) {
|
||||
json_add_amount_msat_only(s, "total_msat", *rs->total_msat);
|
||||
json_add_secret(s, "payment_secret", rs->payment_secret);
|
||||
p->payload->amt_to_forward);
|
||||
json_add_u32(s, "outgoing_cltv_value", p->payload->outgoing_cltv);
|
||||
/* These are specified together in TLV, so only print total_msat
|
||||
* if payment_secret set (ie. modern, and final hop) */
|
||||
if (p->payload->payment_secret) {
|
||||
json_add_amount_msat_only(s, "total_msat",
|
||||
*p->payload->total_msat);
|
||||
json_add_secret(s, "payment_secret",
|
||||
p->payload->payment_secret);
|
||||
}
|
||||
}
|
||||
json_add_hex_talarr(s, "next_onion", p->next_onion);
|
||||
json_add_secret(s, "shared_secret", hin->shared_secret);
|
||||
@ -787,45 +799,6 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p,
|
||||
json_object_end(s);
|
||||
}
|
||||
|
||||
/* Make sure that we can continue with a default action if the htlc_accepted
|
||||
* hook tells us to. This means enforcing that we have the necessary
|
||||
* information to forward, fail or accept, and that the TVL payload is encoded
|
||||
* correctly. */
|
||||
static bool htlc_accepted_can_continue(struct route_step *rs)
|
||||
{
|
||||
if (rs->type == SPHINX_TLV_PAYLOAD &&
|
||||
!tlv_payload_is_valid(rs->payload.tlv, NULL)) {
|
||||
SUPERVERBOSE("Encoding of TLV payload is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* BOLT #4:
|
||||
*
|
||||
* The writer:
|
||||
* - MUST include `amt_to_forward` and `outgoing_cltv_value` for every node.
|
||||
* - MUST include `short_channel_id` for every non-final node.
|
||||
* - MUST NOT include `short_channel_id` for the final node.
|
||||
*
|
||||
* The reader:
|
||||
* - MUST return an error if `amt_to_forward` or `outgoing_cltv_value` are not present.
|
||||
*/
|
||||
if (rs->amt_to_forward == NULL) {
|
||||
SUPERVERBOSE("Missing amt_to_forward in payload");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rs->outgoing_cltv == NULL) {
|
||||
SUPERVERBOSE("Missing outgoing_cltv_value in payload");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rs->nextcase && rs->forward_channel == NULL) {
|
||||
SUPERVERBOSE("Missing short_channel_id in payload");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a plugin answers to the htlc_accepted hook
|
||||
*/
|
||||
@ -846,17 +819,18 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request,
|
||||
|
||||
switch (result) {
|
||||
case htlc_accepted_continue:
|
||||
if (!htlc_accepted_can_continue(rs)) {
|
||||
/* *Now* we barf if it failed to decode */
|
||||
if (!request->payload) {
|
||||
log_debug(channel->log, "Failing HTLC because of an invalid payload");
|
||||
failure_code = WIRE_INVALID_ONION_PAYLOAD;
|
||||
fail_in_htlc(hin, failure_code, NULL, NULL);
|
||||
}else if (rs->nextcase == ONION_FORWARD) {
|
||||
} else if (rs->nextcase == ONION_FORWARD) {
|
||||
struct gossip_resolve *gr = tal(ld, struct gossip_resolve);
|
||||
|
||||
gr->next_onion = serialize_onionpacket(gr, rs->next);
|
||||
gr->next_channel = *rs->forward_channel;
|
||||
gr->amt_to_forward = *rs->amt_to_forward;
|
||||
gr->outgoing_cltv_value = *rs->outgoing_cltv;
|
||||
gr->next_channel = *request->payload->forward_channel;
|
||||
gr->amt_to_forward = request->payload->amt_to_forward;
|
||||
gr->outgoing_cltv_value = request->payload->outgoing_cltv;
|
||||
gr->hin = hin;
|
||||
|
||||
req = towire_gossip_get_channel_peer(tmpctx, &gr->next_channel);
|
||||
@ -867,21 +841,30 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request,
|
||||
channel_resolve_reply, gr);
|
||||
} else
|
||||
handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash,
|
||||
*rs->amt_to_forward,
|
||||
*rs->outgoing_cltv,
|
||||
*rs->total_msat,
|
||||
rs->payment_secret);
|
||||
request->payload->amt_to_forward,
|
||||
request->payload->outgoing_cltv,
|
||||
*request->payload->total_msat,
|
||||
request->payload->payment_secret);
|
||||
break;
|
||||
case htlc_accepted_fail:
|
||||
log_debug(channel->log,
|
||||
"Failing incoming HTLC as instructed by plugin hook");
|
||||
if ((failure_code & UPDATE) && rs->nextcase == ONION_END) {
|
||||
log_broken(channel->log,
|
||||
"htlc_acccepted hook: Can't return failure %u on last hop!",
|
||||
failure_code);
|
||||
failure_code = WIRE_TEMPORARY_NODE_FAILURE;
|
||||
if (failure_code & UPDATE) {
|
||||
if (rs->nextcase == ONION_END) {
|
||||
log_broken(channel->log,
|
||||
"htlc_acccepted hook: Can't return failure %u on last hop!",
|
||||
failure_code);
|
||||
failure_code = WIRE_TEMPORARY_NODE_FAILURE;
|
||||
} else if (!request->payload) {
|
||||
log_broken(channel->log,
|
||||
"htlc_acccepted hook: Can't return failure %u on undecodable onion!",
|
||||
failure_code);
|
||||
failure_code = WIRE_TEMPORARY_NODE_FAILURE;
|
||||
}
|
||||
}
|
||||
fail_in_htlc(hin, failure_code, NULL, rs->forward_channel);
|
||||
fail_in_htlc(hin, failure_code, NULL,
|
||||
request->payload
|
||||
? request->payload->forward_channel : NULL);
|
||||
break;
|
||||
case htlc_accepted_resolve:
|
||||
fulfill_htlc(hin, &payment_preimage);
|
||||
@ -989,6 +972,7 @@ static bool peer_accepted_htlc(struct channel *channel, u64 id,
|
||||
hook_payload = tal(hin, struct htlc_accepted_hook_payload);
|
||||
|
||||
hook_payload->route_step = tal_steal(hook_payload, rs);
|
||||
hook_payload->payload = onion_decode(hook_payload, rs);
|
||||
hook_payload->ld = ld;
|
||||
hook_payload->hin = hin;
|
||||
hook_payload->channel = channel;
|
||||
|
@ -2449,22 +2449,25 @@ def test_createonion_rpc(node_factory):
|
||||
l1 = node_factory.get_node()
|
||||
|
||||
hops = [{
|
||||
"style": "legacy",
|
||||
"pubkey": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619",
|
||||
"payload": "0000000000000000000000000000000000000000"
|
||||
# legacy
|
||||
"payload": "000000000000000000000000000000000000000000000000000000000000000000"
|
||||
}, {
|
||||
"pubkey": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
|
||||
"payload": "0101010101010101000000000000000100000001"
|
||||
# tlv (20 bytes)
|
||||
"payload": "140101010101010101000000000000000100000001"
|
||||
}, {
|
||||
"pubkey": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007",
|
||||
"payload": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
||||
# TLV (256 bytes)
|
||||
"payload": "fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
||||
}, {
|
||||
"pubkey": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",
|
||||
"payload": "0303030303030303000000000000000300000003"
|
||||
# tlv (20 bytes)
|
||||
"payload": "140303030303030303000000000000000300000003"
|
||||
}, {
|
||||
"style": "legacy",
|
||||
"pubkey": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145",
|
||||
"payload": "0404040404040404000000000000000400000004"
|
||||
# legacy
|
||||
"payload": "000404040404040404000000000000000400000004000000000000000000000000"
|
||||
}]
|
||||
|
||||
res = l1.rpc.createonion(hops=hops, assocdata="BB" * 32)
|
||||
@ -2475,6 +2478,7 @@ def test_createonion_rpc(node_factory):
|
||||
session_key="41" * 32)
|
||||
# The trailer is generated using the filler and can be ued as a
|
||||
# checksum. This trailer is from the test-vector in the specs.
|
||||
print(res)
|
||||
assert(res['onion'].endswith('be89e4701eb870f8ed64fafa446c78df3ea'))
|
||||
|
||||
|
||||
@ -2491,7 +2495,8 @@ def test_sendonion_rpc(node_factory):
|
||||
def serialize_payload(n):
|
||||
block, tx, out = n['channel'].split('x')
|
||||
payload = hexlify(struct.pack(
|
||||
"!QQL",
|
||||
"!BQQL",
|
||||
0,
|
||||
int(block) << 40 | int(tx) << 16 | int(out),
|
||||
int(n['amount_msat']),
|
||||
blockheight + n['delay'])).decode('ASCII')
|
||||
@ -2503,14 +2508,12 @@ def test_sendonion_rpc(node_factory):
|
||||
for h, n in zip(route[:-1], route[1:]):
|
||||
# We tell the node h about the parameters to use for n (a.k.a. h + 1)
|
||||
hops.append({
|
||||
"style": "legacy",
|
||||
"pubkey": h['id'],
|
||||
"payload": serialize_payload(n)
|
||||
})
|
||||
|
||||
# The last hop has a special payload:
|
||||
hops.append({
|
||||
"style": "legacy",
|
||||
"pubkey": route[-1]['id'],
|
||||
"payload": serialize_payload(route[-1])
|
||||
})
|
||||
|
@ -395,6 +395,10 @@ enum watch_result onchaind_funding_spent(struct channel *channel UNNEEDED,
|
||||
const struct bitcoin_tx *tx UNNEEDED,
|
||||
u32 blockheight UNNEEDED)
|
||||
{ fprintf(stderr, "onchaind_funding_spent called!\n"); abort(); }
|
||||
/* Generated stub for onion_decode */
|
||||
struct onion_payload *onion_decode(const tal_t *ctx UNNEEDED,
|
||||
const struct route_step *rs UNNEEDED)
|
||||
{ fprintf(stderr, "onion_decode called!\n"); abort(); }
|
||||
/* Generated stub for onion_type_name */
|
||||
const char *onion_type_name(int e UNNEEDED)
|
||||
{ fprintf(stderr, "onion_type_name called!\n"); abort(); }
|
||||
@ -545,10 +549,6 @@ void subd_req_(const tal_t *ctx UNNEEDED,
|
||||
/* Generated stub for subd_send_msg */
|
||||
void subd_send_msg(struct subd *sd UNNEEDED, const u8 *msg_out UNNEEDED)
|
||||
{ fprintf(stderr, "subd_send_msg called!\n"); abort(); }
|
||||
/* Generated stub for tlv_payload_is_valid */
|
||||
bool tlv_payload_is_valid(const struct tlv_tlv_payload *record UNNEEDED,
|
||||
size_t *err_index UNNEEDED)
|
||||
{ fprintf(stderr, "tlv_payload_is_valid called!\n"); abort(); }
|
||||
/* Generated stub for topology_add_sync_waiter_ */
|
||||
void topology_add_sync_waiter_(const tal_t *ctx UNNEEDED,
|
||||
struct chain_topology *topo UNNEEDED,
|
||||
|
Loading…
Reference in New Issue
Block a user