Skip to content

Commit

Permalink
bolt11: reorder invoice production to match test vectors.
Browse files Browse the repository at this point in the history
After this, we can exactly reproduce the vectors (in DEVELOPER mode).

1. Move payment_metadata position to match test vector.
2. Create flag to suppress `c` field production.
3. Some vectors put secret before payment_hash, hack that in.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
rustyrussell committed Mar 22, 2022
1 parent cf45147 commit 7bbe38e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 13 deletions.
42 changes: 36 additions & 6 deletions common/bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@
#include <inttypes.h>
#include <lightningd/lightningd.h>

#if DEVELOPER
bool dev_bolt11_no_c_generation;

/* For test vectors, older ones put p before s. */
static bool modern_order(const struct bolt11 *b11)
{
if (!b11->description)
return true;
if (streq(b11->description,
"Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items"))
return false;
if (streq(b11->description, "coffee beans"))
return false;
if (streq(b11->description, "payment metadata inside"))
return false;
return true;
}
#endif

struct multiplier {
const char letter;
/* We can't represent p postfix to msat, so we multiply this by 10 */
Expand Down Expand Up @@ -996,6 +1015,8 @@ static void encode_x(u5 **data, u64 expiry)

static void encode_c(u5 **data, u16 min_final_cltv_expiry)
{
if (IFDEV(dev_bolt11_no_c_generation, false))
return;
push_varlen_field(data, 'c', min_final_cltv_expiry);
}

Expand Down Expand Up @@ -1154,6 +1175,13 @@ char *bolt11_encode_(const tal_t *ctx,
*/
push_varlen_uint(&data, b11->timestamp, 35);

/* This is a hack to match the test vectors, *some* of which
* order differently! */
if (IFDEV(modern_order(b11), true)) {
if (b11->payment_secret)
encode_s(&data, b11->payment_secret);
}

/* BOLT #11:
*
* if a writer offers more than one of any field type,
Expand All @@ -1166,26 +1194,28 @@ char *bolt11_encode_(const tal_t *ctx,
if (b11->description)
encode_d(&data, b11->description);

if (b11->metadata)
encode_m(&data, b11->metadata);

if (b11->description_hash)
encode_h(&data, b11->description_hash);

if (n_field)
encode_n(&data, &b11->receiver_id);

if (IFDEV(!modern_order(b11), false)) {
if (b11->payment_secret)
encode_s(&data, b11->payment_secret);
}

if (b11->expiry != DEFAULT_X)
encode_x(&data, b11->expiry);

if (b11->metadata)
encode_m(&data, b11->metadata);

/* BOLT #11:
* - MUST include one `c` field (`min_final_cltv_expiry`).
*/
encode_c(&data, b11->min_final_cltv_expiry);

if (b11->payment_secret)
encode_s(&data, b11->payment_secret);

for (size_t i = 0; i < tal_count(b11->fallbacks); i++)
encode_f(&data, b11->fallbacks[i]);

Expand Down
5 changes: 5 additions & 0 deletions common/bolt11.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ char *bolt11_encode_(const tal_t *ctx,
secp256k1_ecdsa_recoverable_signature *rsig), \
(arg))

#if DEVELOPER
/* Flag for tests to suppress `min_final_cltv_expiry` field generation, to match test vectors */
extern bool dev_bolt11_no_c_generation;
#endif

#endif /* LIGHTNING_COMMON_BOLT11_H */
20 changes: 13 additions & 7 deletions common/test/run-bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ static void test_b11(const char *b11str,
{
struct bolt11 *b11;
char *fail;
char *reproduce;
struct bolt11_field *b11_extra, *expect_extra;

b11 = bolt11_decode(tmpctx, b11str, NULL, hashed_desc,
Expand Down Expand Up @@ -115,20 +114,22 @@ static void test_b11(const char *b11str,
assert(!expect_extra);

/* FIXME: Spec changed to require c fields, but test vectors don't! */
if (b11->min_final_cltv_expiry == 18)
return;

#if DEVELOPER
char *reproduce;

dev_bolt11_no_c_generation = (b11->min_final_cltv_expiry == 18);

/* Also blockstream store example signature doesn't match? */
/* Re-encode to check */
reproduce = bolt11_encode(tmpctx, b11, false, test_sign, NULL);
#if 0
for (size_t i = 0; i < strlen(reproduce); i++) {
if (reproduce[i] != b11str[i]
&& reproduce[i] != tolower(b11str[i]))
abort();
}
#endif
assert(strlen(reproduce) == strlen(b11str));
#endif
}

int main(int argc, char *argv[])
Expand Down Expand Up @@ -478,8 +479,13 @@ int main(int argc, char *argv[])
set_feature_bit(&b11->features, 100);
badstr = bolt11_encode(tmpctx, b11, false, test_sign, NULL);

/* FIXME: above has missing c field! */
assert(streq(badstr, "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeescqpjsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqjckhuumq7mk7pdua9s6umdg34sjhlju9qgcvclxl35guw3dhhyrrtnmudz3kspyqk6k6r7thyzyrleq9s9lmgh59zlc49mc3nd7ngecqllqtym"));
/* Needs DEVELOPER to munge this into BOLT example order! */
#if DEVELOPER
assert(streq(badstr, "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"));
#else
assert(streq(badstr, "lnbc25m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeescqpj9q4psqqqqqqqqqqqqqqqqsgqf0nf0agw8xncpemlreh8wl0z5exhz3pky094lu7pf62nvcxq2vljzhhw69xfdftrgm0jklut3h25nlsfw5prz4c0pjy46xyer0k85hqpnathfq"));
#endif

/* Empty set of allowed bits, ensures this fails! */
fset = tal(tmpctx, struct feature_set);
fset->bits[BOLT11_FEATURE] = tal_arr(fset, u8, 0);
Expand Down

0 comments on commit 7bbe38e

Please sign in to comment.