From 78b5a373f9c8b280e4c8efa891eb6a64366121c3 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 30 Mar 2022 14:31:40 +1030 Subject: [PATCH] bolt11: reorder invoice production to match test vectors. 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 --- common/bolt11.c | 42 ++++++++++++++++++++++++++++++++++------ common/bolt11.h | 5 +++++ common/test/run-bolt11.c | 20 ++++++++++++------- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/common/bolt11.c b/common/bolt11.c index a4cc1df49b04..29fd34fef066 100644 --- a/common/bolt11.c +++ b/common/bolt11.c @@ -11,6 +11,25 @@ #include #include +#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 */ @@ -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); } @@ -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, @@ -1174,23 +1202,25 @@ char *bolt11_encode_(const tal_t *ctx, else if (b11->description) encode_d(&data, b11->description); + if (b11->metadata) + encode_m(&data, b11->metadata); + 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]); diff --git a/common/bolt11.h b/common/bolt11.h index 19b87f78fc8c..b561cf569ffc 100644 --- a/common/bolt11.h +++ b/common/bolt11.h @@ -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 */ diff --git a/common/test/run-bolt11.c b/common/test/run-bolt11.c index 488b848cdd62..d4b2f6aa48c9 100644 --- a/common/test/run-bolt11.c +++ b/common/test/run-bolt11.c @@ -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, @@ -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[]) @@ -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);