Browse Source

sphinx: Add function to add a new v0 hop to a sphinx_path

This is just taking the existing serialization code and repackaging it in a
more useful form.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
pull/2938/head
Christian Decker 6 years ago
committed by Rusty Russell
parent
commit
a0a1a1f752
  1. 147
      common/sphinx.c
  2. 12
      common/sphinx.h
  3. 3
      common/test/run-sphinx.c
  4. 19
      devtools/onion.c
  5. 35
      lightningd/pay.c

147
common/sphinx.c

@ -4,6 +4,7 @@
#include <ccan/crypto/ripemd160/ripemd160.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/mem/mem.h>
#include <common/node_id.h>
#include <common/sphinx.h>
#include <common/utils.h>
@ -85,6 +86,67 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx,
return sp;
}
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 varint 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->realm == 0x00)
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 varint
* describing the length and HMAC. */
return vsize + size + HMAC_SIZE;
}
static size_t sphinx_path_payloads_size(const struct sphinx_path *path)
{
size_t size = 0;
for (size_t i=0; i<tal_count(path->hops); i++)
size += sphinx_hop_size(&path->hops[i]);
return size;
}
/**
* Add a raw payload hop to the path.
*/
static void sphinx_add_raw_hop(struct sphinx_path *path,
const struct pubkey *pubkey, u8 realm,
const u8 *payload)
{
struct sphinx_hop sp;
sp.payload = payload;
sp.realm = realm;
sp.pubkey = *pubkey;
tal_arr_expand(&path->hops, sp);
assert(sphinx_path_payloads_size(path) <= ROUTING_INFO_SIZE);
}
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)
{
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);
sphinx_add_raw_hop(path, pubkey, 0, buf);
}
/* Small helper to append data to a buffer and update the position
* into the buffer
*/
@ -247,28 +309,24 @@ static void compute_blinding_factor(const struct pubkey *key,
memcpy(res, &temp, 32);
}
static bool blind_group_element(
struct pubkey *blindedelement,
const struct pubkey *pubkey,
const u8 blind[BLINDING_FACTOR_SIZE])
static bool blind_group_element(struct pubkey *blindedelement,
const struct pubkey *pubkey,
const u8 blind[BLINDING_FACTOR_SIZE])
{
/* tweak_mul is inplace so copy first. */
if (pubkey != blindedelement)
*blindedelement = *pubkey;
if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &blindedelement->pubkey, blind) != 1)
if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx,
&blindedelement->pubkey, blind) != 1)
return false;
return true;
}
static bool create_shared_secret(
u8 *secret,
const struct pubkey *pubkey,
const u8 *sessionkey)
static bool create_shared_secret(u8 *secret, const struct pubkey *pubkey,
const struct secret *session_key)
{
if (secp256k1_ecdh(secp256k1_ctx, secret, &pubkey->pubkey, sessionkey,
NULL, NULL)
!= 1)
if (secp256k1_ecdh(secp256k1_ctx, secret, &pubkey->pubkey,
session_key->data, NULL, NULL) != 1)
return false;
return true;
}
@ -279,7 +337,7 @@ bool onion_shared_secret(
const struct privkey *privkey)
{
return create_shared_secret(secret, &packet->ephemeralkey,
privkey->secret.data);
&privkey->secret);
}
static void generate_key_set(const u8 secret[SHARED_SECRET_SIZE],
@ -294,19 +352,21 @@ static void generate_key_set(const u8 secret[SHARED_SECRET_SIZE],
static struct hop_params *generate_hop_params(
const tal_t *ctx,
const u8 *sessionkey,
struct pubkey path[])
struct sphinx_path *path)
{
int i, j, num_hops = tal_count(path);
int i, j, num_hops = tal_count(path->hops);
struct pubkey temp;
u8 blind[BLINDING_FACTOR_SIZE];
struct hop_params *params = tal_arr(ctx, struct hop_params, num_hops);
/* Initialize the first hop with the raw information */
if (secp256k1_ec_pubkey_create(
secp256k1_ctx, &params[0].ephemeralkey.pubkey, sessionkey) != 1)
if (secp256k1_ec_pubkey_create(secp256k1_ctx,
&params[0].ephemeralkey.pubkey,
path->session_key->data) != 1)
return NULL;
if (!create_shared_secret(params[0].secret, &path[0], sessionkey))
if (!create_shared_secret(params[0].secret, &path->hops[0].pubkey,
path->session_key))
return NULL;
compute_blinding_factor(
@ -327,7 +387,7 @@ static struct hop_params *generate_hop_params(
* Order is indifferent, multiplication is commutative.
*/
memcpy(&blind, sessionkey, 32);
temp = path[i];
temp = path->hops[i].pubkey;
if (!blind_group_element(&temp, &temp, blind))
return NULL;
for (j = 0; j < i; j++)
@ -353,19 +413,6 @@ static struct hop_params *generate_hop_params(
return params;
}
static void serialize_hop_data(tal_t *ctx, u8 *dst, const struct hop_data *data)
{
u8 *buf = tal_arr(ctx, u8, 0);
towire_u8(&buf, data->realm);
towire_short_channel_id(&buf, &data->channel_id);
towire_amount_msat(&buf, data->amt_forward);
towire_u32(&buf, data->outgoing_cltv);
towire_pad(&buf, 12);
towire(&buf, data->hmac, HMAC_SIZE);
memcpy(dst, buf, tal_count(buf));
tal_free(buf);
}
static void deserialize_hop_data(struct hop_data *data, const u8 *src)
{
const u8 *cursor = src;
@ -378,25 +425,35 @@ static void deserialize_hop_data(struct hop_data *data, const u8 *src)
fromwire(&cursor, &max, &data->hmac, HMAC_SIZE);
}
static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop)
{
memset(dest, 0, FRAME_SIZE);
dest[0] = hop->realm;
memcpy(dest + 1, hop->payload, tal_bytelen(hop->payload));
memcpy(dest + FRAME_SIZE - HMAC_SIZE, hop->hmac, HMAC_SIZE);
}
struct onionpacket *create_onionpacket(
const tal_t *ctx,
struct pubkey *path,
struct hop_data hops_data[],
const u8 *sessionkey,
const u8 *assocdata,
const size_t assocdatalen,
struct sphinx_path *sp,
struct secret **path_secrets
)
{
struct onionpacket *packet = talz(ctx, struct onionpacket);
int i, num_hops = tal_count(path);
int i, num_hops = tal_count(sp->hops);
u8 filler[(num_hops - 1) * FRAME_SIZE];
struct keyset keys;
u8 nexthmac[HMAC_SIZE];
u8 stream[ROUTING_INFO_SIZE];
struct hop_params *params = generate_hop_params(ctx, sessionkey, path);
struct hop_params *params;
struct secret *secrets = tal_arr(ctx, struct secret, num_hops);
if (sp->session_key == NULL) {
sp->session_key = tal(sp, struct secret);
randombytes_buf(sp->session_key, sizeof(struct secret));
}
params = generate_hop_params(ctx, sp->session_key->data, sp);
if (!params) {
tal_free(packet);
tal_free(secrets);
@ -410,15 +467,15 @@ struct onionpacket *create_onionpacket(
"rho", 3, num_hops, params);
for (i = num_hops - 1; i >= 0; i--) {
memcpy(hops_data[i].hmac, nexthmac, HMAC_SIZE);
hops_data[i].realm = 0;
memcpy(sp->hops[i].hmac, nexthmac, HMAC_SIZE);
generate_key_set(params[i].secret, &keys);
generate_cipher_stream(stream, keys.rho, ROUTING_INFO_SIZE);
/* Rightshift mix-header by 2*HMAC_SIZE */
/* Rightshift mix-header by FRAME_SIZE */
memmove(packet->routinginfo + FRAME_SIZE, packet->routinginfo,
ROUTING_INFO_SIZE - FRAME_SIZE);
serialize_hop_data(packet, packet->routinginfo, &hops_data[i]);
sphinx_write_frame(packet->routinginfo, &sp->hops[i]);
xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE);
if (i == num_hops - 1) {
@ -426,7 +483,7 @@ struct onionpacket *create_onionpacket(
memcpy(packet->routinginfo + len, filler, sizeof(filler));
}
compute_packet_hmac(packet, assocdata, assocdatalen, keys.mu,
compute_packet_hmac(packet, sp->associated_data, tal_bytelen(sp->associated_data), keys.mu,
nexthmac);
}
memcpy(packet->mac, nexthmac, sizeof(nexthmac));

12
common/sphinx.h

@ -110,11 +110,7 @@ struct route_step {
*/
struct onionpacket *create_onionpacket(
const tal_t * ctx,
struct pubkey path[],
struct hop_data hops_data[],
const u8 * sessionkey,
const u8 *assocdata,
const size_t assocdatalen,
struct sphinx_path *sp,
struct secret **path_secrets
);
@ -231,4 +227,10 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx,
const u8 *associated_data,
const struct secret *session_key);
/**
* Add a V0 (Realm 0) single frame hop to the path.
*/
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);
#endif /* LIGHTNING_COMMON_SPHINX_H */

3
common/test/run-sphinx.c

@ -56,6 +56,9 @@ void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED)
/* Generated stub for towire_u32 */
void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED)
{ fprintf(stderr, "towire_u32 called!\n"); abort(); }
/* Generated stub for towire_u64 */
void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED)
{ fprintf(stderr, "towire_u64 called!\n"); abort(); }
/* Generated stub for towire_u8 */
void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED)
{ fprintf(stderr, "towire_u8 called!\n"); abort(); }

19
devtools/onion.c

@ -3,6 +3,7 @@
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/short_types/short_types.h>
#include <ccan/str/hex/hex.h>
#include <common/amount.h>
#include <common/sphinx.h>
#include <common/utils.h>
#include <err.h>
@ -19,11 +20,15 @@ static void do_generate(int argc, char **argv,
int num_hops = argc - 1;
struct pubkey *path = tal_arr(ctx, struct pubkey, num_hops);
u8 privkeys[argc - 1][32];
u8 sessionkey[32];
struct secret session_key;
struct hop_data hops_data[num_hops];
struct secret *shared_secrets;
struct sphinx_path *sp;
memset(&sessionkey, 'A', sizeof(sessionkey));
assocdata = tal_arr(ctx, u8, 32);
memset(&session_key, 'A', sizeof(struct secret));
sp = sphinx_path_new_with_key(ctx, assocdata, &session_key);
for (int i = 0; i < num_hops; i++) {
size_t klen = strcspn(argv[1 + i], "/");
@ -33,8 +38,9 @@ static void do_generate(int argc, char **argv,
if (secp256k1_ec_pubkey_create(secp256k1_ctx, &path[i].pubkey,
privkeys[i]) != 1)
errx(1, "Could not decode pubkey");
printf("# Node %d pubkey %s\n",
i, secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey));
fprintf(stderr, "Node %d pubkey %s\n", i,
secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey));
memset(&hops_data[i], 0, sizeof(hops_data[i]));
if (argv[1 + i][klen] != '\0') {
/* FIXME: Generic realm support, not this hack! */
@ -76,11 +82,12 @@ static void do_generate(int argc, char **argv,
hops_data[i].amt_forward.millisatoshis = i; /* Raw: test code */
hops_data[i].outgoing_cltv = i;
}
fprintf(stderr, "Hopdata %d: %s\n", i, tal_hexstr(NULL, &hops_data[i], sizeof(hops_data[i])));
sphinx_add_v0_hop(sp, &path[i], &hops_data[i].channel_id, hops_data[i].amt_forward, i);
}
struct onionpacket *res =
create_onionpacket(ctx, path, hops_data, sessionkey,
assocdata, ASSOC_DATA_SIZE, &shared_secrets);
create_onionpacket(ctx, sp, &shared_secrets);
u8 *serialized = serialize_onionpacket(ctx, res);
if (!serialized)

35
lightningd/pay.c

@ -590,37 +590,36 @@ send_payment(struct lightningd *ld,
enum onion_type failcode;
size_t i, n_hops = tal_count(route);
struct hop_data *hop_data = tal_arr(tmpctx, struct hop_data, n_hops);
struct pubkey *path = tal_arr(tmpctx, struct pubkey, n_hops);
struct node_id *ids = tal_arr(tmpctx, struct node_id, n_hops);
struct wallet_payment *payment = NULL;
struct htlc_out *hout;
struct short_channel_id *channels;
struct routing_failure *fail;
struct channel *channel;
struct sphinx_path *path;
struct short_channel_id finalscid;
struct pubkey pubkey;
bool ret;
/* Expiry for HTLCs is absolute. And add one to give some margin. */
base_expiry = get_block_height(ld->topology) + 1;
/* Extract IDs for each hop: create_onionpacket wants array of *keys*,
* and wallet wants continuous array of node_ids */
for (i = 0; i < n_hops; i++) {
path = sphinx_path_new(tmpctx, rhash->u.u8);
/* Extract IDs for each hop: create_onionpacket wants array. */
for (i = 0; i < n_hops; i++)
ids[i] = route[i].nodeid;
/* JSON parsing checked these were valid, so Shouldn't Happen */
if (!pubkey_from_node_id(&path[i], &ids[i])) {
return command_fail(cmd, PAY_UNSPECIFIED_ERROR,
"Invalid nodeid %s",
type_to_string(tmpctx,
struct node_id,
&ids[i]));
}
}
/* Copy hop_data[n] from route[n+1] (ie. where it goes next) */
for (i = 0; i < n_hops - 1; i++) {
ret = pubkey_from_node_id(&pubkey, &ids[i]);
assert(ret);
hop_data[i].realm = 0;
hop_data[i].channel_id = route[i+1].channel_id;
hop_data[i].amt_forward = route[i+1].amount;
hop_data[i].outgoing_cltv = base_expiry + route[i+1].delay;
sphinx_add_v0_hop(path, &pubkey, &route[i + 1].channel_id,
route[i + 1].amount,
base_expiry + route[i + 1].delay);
}
/* And finally set the final hop to the special values in
@ -630,6 +629,13 @@ send_payment(struct lightningd *ld,
memset(&hop_data[i].channel_id, 0, sizeof(struct short_channel_id));
hop_data[i].amt_forward = route[i].amount;
memset(&finalscid, 0, sizeof(struct short_channel_id));
ret = pubkey_from_node_id(&pubkey, &ids[i]);
assert(ret);
sphinx_add_v0_hop(path, &pubkey, &finalscid,
route[i].amount,
base_expiry + route[i].delay);
/* Now, do we already have a payment? */
payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash);
if (payment) {
@ -679,8 +685,7 @@ send_payment(struct lightningd *ld,
randombytes_buf(&sessionkey, sizeof(sessionkey));
/* Onion will carry us from first peer onwards. */
packet = create_onionpacket(tmpctx, path, hop_data, sessionkey, rhash->u.u8,
sizeof(struct sha256), &path_secrets);
packet = create_onionpacket(tmpctx, path, &path_secrets);
onion = serialize_onionpacket(tmpctx, packet);
log_info(ld->log, "Sending %s over %zu hops to deliver %s",

Loading…
Cancel
Save