Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable hop payload size for sphinx #2689

Merged
merged 13 commits into from
Jul 30, 2019

Conversation

cdecker
Copy link
Member

@cdecker cdecker commented May 29, 2019

This is the competing proposal to #2363. It implements the variable size hop_payloads discussed duriing the last IRC spec meeting.

It is implemented as fixups based on that PR, so you should be able review just the fixup! commits and get a good picture of the differences. I also cleaned up all the common mentions of frames in the final commit, but left it in other places to facilitate reviews.

Closes #2363

Copy link
Collaborator

@jb55 jb55 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few things that caught my eye

if (hop->realm == 0x00)
return 65;

if (size < 0xFD)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment would be helpful here

common/sphinx.c Outdated

/* The hop must accomodate the hop_payload, as well as the varint
* describing the length and HMAC. */
return vsize + size + HMAC_SIZE;;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra semicolon

common/sphinx.c Outdated
@@ -135,10 +137,14 @@ 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 padding[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps this could be const u8 padding[]

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));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is sizeof(padding) instead of ARRAY_SIZE the preferred way to do this with byte-sized elements? not sure if the codebase has a particular style preference here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARRAY_SIZE is basically always preferable when you have an array. Unlike sizeof(), it will break compile if the argument is a pointer instead of an array, due to refactoring.

pos += varint_put(dest+pos, raw_size);

memcpy(dest + pos, hop->payload, raw_size);
pos += raw_size;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is incremented but not used afterwards, is that intended?

common/sphinx.c Outdated

memcpy(dest + pos, hop->payload, raw_size);
pos += raw_size;
memcpy(dest + hop_size - HMAC_SIZE, hop->hmac, HMAC_SIZE);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's hard to tell what's going on here, but perhaps there could be an assert(pos < dest + hop_size - HMAC_SIZE) if I'm reading this correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm pretty sure this should be "memcpy(dest + pos, hop->hmac, HMAC_SIZE); pos += HMAC_SIZE; assert(pos == hop_size);"

ALso, I don't think we should be memset() dest to 0 at the top: I'd prefer to memset just the expected padding in the legacy path. That way valgrind will catch any bugs if we miss some bytes somehow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made more explicit:

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

😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still like to see an assert(pos == hop_size); at the end?

Copy link
Contributor

@rustyrussell rustyrussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some good feedback from @jb55, and one nasty change from me re: endianness.

I don't think devtools/onion should do JSON, I think common/test/run-sphinx (or a new file) should.

common/sphinx.c Outdated

memcpy(dest + pos, hop->payload, raw_size);
pos += raw_size;
memcpy(dest + hop_size - HMAC_SIZE, hop->hmac, HMAC_SIZE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm pretty sure this should be "memcpy(dest + pos, hop->hmac, HMAC_SIZE); pos += HMAC_SIZE; assert(pos == hop_size);"

ALso, I don't think we should be memset() dest to 0 at the top: I'd prefer to memset just the expected padding in the legacy path. That way valgrind will catch any bugs if we miss some bytes somehow.

/* In addition to the raw payload we need to also shift the
* length encoding itself and the HMAC away. */
vsize = varint_get(paddedheader, 3, &shift_size);
shift_size += vsize + HMAC_SIZE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong on several levels.

  1. I worry what happens if we don't have 3 bytes here, can we be tricked into reading off the end?
  2. This uses bitcoin's varint and we are now using BigSize which is big-endian.
  3. shift_size needs to be sanity checked carefully!

Copy link
Member Author

@cdecker cdecker Jul 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I worry what happens if we don't have 3 bytes here, can we be tricked into reading off the end?

No, we have paddedheader[2*ROUTING_INFO_SIZE];, i.e., 2600 bytes here.

  1. This uses bitcoin's varint and we are now using BigSize which is big-endian.

Yep, fixing up

  1. shift_size needs to be sanity checked carefully!

I went and just abort when shift_size >= ROUTING_INFO_SIZE since that'd shift out all non-filler infos and leave us with garbage at the end anyway :-)

handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash,
hop_data->amt_forward,
hop_data->outgoing_cltv);
/* if (rs->nextcase == ONION_FORWARD) {
struct gossip_resolve *gr =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erk, please delete don't comment out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, must've slipped in

@@ -1,10 +1,16 @@
#include <ccan/mem/mem.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erk, I just repurposed onion.c as a generic onion builder, which will clash horribly with this.

My approach with the TLV tests (which are also JSON) was to quote the spec in the test case (so if it's wrong or changes, check-source points it out) then pulls the JSON out of itself. We should generalize that, IMHO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, ran into quite a bit of trouble rebasing, but it's working fine now :-)

@cdecker
Copy link
Member Author

cdecker commented Jul 18, 2019

Rebased and fixed up to reflect feedback 😉

@jb55
Copy link
Collaborator

jb55 commented Jul 18, 2019

fixup ACK on my comments!

Copy link
Contributor

@rustyrussell rustyrussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes only, I'm eager to apply this!

common/sphinx.c Outdated

memcpy(dest + pos, hop->payload, raw_size);
pos += raw_size;
memcpy(dest + hop_size - HMAC_SIZE, hop->hmac, HMAC_SIZE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still like to see an assert(pos == hop_size); at the end?

size_t bigsize_put(u8 buf[VARINT_MAX_LEN], varint_t v);

/* Big-endian variant of varint_get, used in lightning */
size_t bigsize_get(const u8 *p, size_t max, varint_t *val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These routines don't belong under bitcoin/, and since the last merge they're now redundant: we now have fromwire_bigsize() and towire_bigsize().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that they don't belong here, but they are useful since fromwire_* / towire_* require tal_arr which they can extend, whereas the sphinx operations require in-place operations. I tried using the fromwire and towire variants, but it turned out to involve way more work than just implementing it again.

I can move the functions in a later cleanup PR.

common/sphinx.c Outdated
if (hop->realm != 0x00)
return false;
#endif

memset(dest, 0, hop_size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to delete this...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It get's remove in a later commit :-)

devtools/onion.c Outdated
if (secp256k1_ec_pubkey_create(secp256k1_ctx, &path[i].pubkey,
privkeys[i]) != 1)
errx(1, "Could not decode pubkey");
assert(klen == 2 * PUBKEY_LEN || klen == 2 * PRIVKEY_LEN);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this assert, since you give a proper message below?

errx(1, "Could not decode pubkey");
assert(klen == 2 * PUBKEY_LEN || klen == 2 * PRIVKEY_LEN);
if (klen == 2 * PRIVKEY_LEN) {
if (!hex_decode(argv[1 + i], klen, rawprivkey, PRIVKEY_LEN))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would combine these two, for simplicity. hex_decode() will fail if the wrong length, and it avoids magic 2 *. The other option would be if (hex_data_size(klen) == PRIVKEY_LEN) but it still seems redundant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to use hex_data_size and pubkey_from_hexstr to do the conversion in one go. Would a counterpart for privkeys be desirable as well (privkey_from_hexstr)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, hex_decode does this test internally, so it's redundant. But not harmful, and I really want this merged :)

devtools/onion.c Outdated
fprintf(stderr,
"Provided key is neither a pubkey nor a "
"privkey: %s\n",
argv[1 + i]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errx()

devtools/onion.c Outdated
@@ -12,32 +13,55 @@
#include <unistd.h>

#define ASSOC_DATA_SIZE 32
#define PUBKEY_LEN 33
#define PRIVKEY_LEN 32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PUBKEY_CMPR_LEN from bitcoin/pubkey.h? Hmm we don't have a convenient PRIVKEY_LEN though...

If you're bored you could add one to bitcoin/privkey.h :)

For the multi-frame support we need to introduce the FRAME_SIZE parameter and
I took the opportunity to fix up some of the naming.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
`struct sphinx_path` serves as a container for all the routing related
information, with a couple of constructors that can be used for normal
operation or testing (with pre-determined `session_key`).

Signed-off-by: Christian Decker <decker.christian@gmail.com>
@cdecker
Copy link
Member Author

cdecker commented Jul 26, 2019

Clean rebase to get code back on top of master, now doing a cleanup sweep to address @rustyrussell's feedback.

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>
This was a mismatch between the go tool and this test tool so far. Just
aligning the tools to allows for easier testing.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
Just some reorganizations and clarifications before we add the multi-frame
support.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
Shouldn't be used directly, but really useful for testing, since we can just
cram a huge payload in without having to be valid. And we don't have a TLV
spec yet.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is all it takes on the read side to use multiple frames. We are
overshooting the padding a bit since we can at most use 16 additional frames,
but ChaCha20 is cheap.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
The `runtest` command takes a JSON onion spec, creates the onion and decodes
it with the provided private keys. It is fully configurable and can be used
for the test-vectors in the spec.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
The realm has lost significance, so let's unify this into the type.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
See lightning/bolts#619 and
lightning/bolts#619 for discussion.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
Simplifying some operations, erroring in some cases and moving to global
defines for constants.

Suggested-by: Rusty Russell <@rustyrussell>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
@cdecker
Copy link
Member Author

cdecker commented Jul 26, 2019

@rustyrussell I added the last commit to address your feedback. I think the last open question is the use of bigsize_put and bigsize_get which need to move into common/ and whether we want to keep them at all, or whether we prefer going the tal_arr + towire_bigsize + memcpy + tal_free route. Alternatively we could have towire_bigsize and fromwire_bigsize use them internally, but I'm not sure how much logic is then left to the wrapper.

Copy link
Contributor

@rustyrussell rustyrussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack ddf7da1

errx(1, "Could not decode pubkey");
assert(klen == 2 * PUBKEY_LEN || klen == 2 * PRIVKEY_LEN);
if (klen == 2 * PRIVKEY_LEN) {
if (!hex_decode(argv[1 + i], klen, rawprivkey, PRIVKEY_LEN))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, hex_decode does this test internally, so it's redundant. But not harmful, and I really want this merged :)

@rustyrussell rustyrussell merged commit 581694f into ElementsProject:master Jul 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants