From 10f333b041f04f5042b0910c0d6366b1f40f13e7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 26 Feb 2025 17:27:01 +1030 Subject: [PATCH] devtools/bolt12-cli: fix interpretation of blindedpay, amounts. The old blindedpay fields would have an entry per hop, but that was changed to a single per-path entry. The devtools hadn't caught up. Similarly, it didn't like descriptionless offer fields in invreqs and invoices. Signed-off-by: Rusty Russell --- devtools/bolt12-cli.c | 40 ++++++++++++++++++++++++---------------- tests/test_misc.py | 10 ++++++++++ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index 69f6a67ca..1965dfc98 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -266,8 +266,6 @@ static bool print_blindedpaths(const char *fieldname, struct blinded_path **paths, struct blinded_payinfo **blindedpay) { - size_t bp_idx = 0; - for (size_t i = 0; i < tal_count(paths); i++) { struct blinded_path_hop **p = paths[i]->path; printf("%s %zu/%zu: first_path_key %s ", @@ -282,22 +280,24 @@ static bool print_blindedpaths(const char *fieldname, fmt_pubkey(tmpctx, &p[j]->blinded_node_id), tal_hex(tmpctx, p[j]->encrypted_recipient_data)); - if (blindedpay) { - if (bp_idx < tal_count(blindedpay)) - printf("fee=%u/%u,cltv=%u,features=%s", - blindedpay[bp_idx]->fee_base_msat, - blindedpay[bp_idx]->fee_proportional_millionths, - blindedpay[bp_idx]->cltv_expiry_delta, - tal_hex(tmpctx, - blindedpay[bp_idx]->features)); - bp_idx++; - } + } + if (i < tal_count(blindedpay)) { + printf(" blindedpay: fee=%u/%u,cltv=%u,features=%s", + blindedpay[i]->fee_base_msat, + blindedpay[i]->fee_proportional_millionths, + blindedpay[i]->cltv_expiry_delta, + tal_hex(tmpctx, blindedpay[i]->features)); } printf("\n"); } - if (blindedpay && tal_count(blindedpay) != bp_idx) { + /* BOLT #12: + * - MUST reject the invoice if `invoice_blindedpay` does not + * contain exactly one `blinded_payinfo` per + * `invoice_paths`.`blinded_path`. + */ + if (blindedpay && tal_count(blindedpay) != tal_count(paths)) { fprintf(stderr, "Expected %zu blindedpay fields, got %zu\n", - bp_idx, tal_count(blindedpay)); + tal_count(paths), tal_count(blindedpay)); return false; } return true; @@ -893,7 +893,11 @@ int main(int argc, char *argv[]) well_formed &= print_offer_amount(invreq->offer_chains, invreq->offer_currency, *invreq->offer_amount); - if (must_have(invreq, offer_description)) + if (invreq->offer_amount && !invreq->offer_description) { + fprintf(stderr, "Missing offer_description (with offer_amount)\n"); + well_formed = false; + } + if (invreq->offer_description) well_formed &= print_utf8("offer_description", invreq->offer_description); if (invreq->offer_features) print_features("offer_features", invreq->offer_features); @@ -971,7 +975,11 @@ int main(int argc, char *argv[]) well_formed &= print_offer_amount(invoice->offer_chains, invoice->offer_currency, *invoice->offer_amount); - if (must_have(invoice, offer_description)) + if (invoice->offer_amount && !invoice->offer_description) { + fprintf(stderr, "Missing offer_description (with offer_amount)\n"); + well_formed = false; + } + if (invoice->offer_description) well_formed &= print_utf8("offer_description", invoice->offer_description); if (invoice->offer_features) print_features("offer_features", invoice->offer_features); diff --git a/tests/test_misc.py b/tests/test_misc.py index b8e26ff05..d21c5cff7 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -4680,3 +4680,13 @@ def test_listaddresses(node_factory): # start and limit from future addresses = l1.rpc.listaddresses(start=21, limit=5)["addresses"] assert len(addresses) == 0 + + +def test_bolt12_invoice_decode(node_factory): + """Test decode of a real invoice.""" + l1 = node_factory.get_node() + + inv = 'lni1qqg26r8checx54jang4393z59wa6293pqvvhnlnvurnfanndnxjtcjnmxrkj92xtsupa6lwjm7hkr8s8zflqk5sz82v9gqzcyypsvsgehgxpnlapwnahwt89fjrad8nzlxn0z0dmn46gqpk2qd2n8mdql5q0uqh34ry8vpl5zhy0ytqqtycqya6eg802fzrfec3sj6hj0vx0mnqtdypsz43dexx9tyt8ak270h957cedaw952ryqjzwgmzuvunqv53878sqzqg2upz426juplphy68fqqafavzzqm6msnsnsehgjmsnqhv39v7v3cqzraklvv0rl4sg654t44ujvetklp6urayt9vjprjgy35paec0a373khaj9r6cqg5x6u4qqvg24eqj3nn8gpfx3tv0075g5mmz6k6jezhnx6wh6s9atydz30ektzmhexua6ayuzuq53mayp8d5h8yhfdf373kzyzvecuqqep0zy8qljenhelz0awkws6p4llvg5tgcty6ev53l6pmgeqd5zmqgryr5wm968uchxmwr2k86qqtyymgze2y8qqqqqqqqqqq86qpgsqqqqqqqqqq05qqqqqqpq3qqqqqqq2gpr8hhtq82pqcel324ms8wd6vwphtm33l883xmh7hm2dwruc4v95xvn85kq46rw25q36nzhqxqsqqzczzqce08lxec8xnm8xmxdyh398kv8dy25vhpcrm47a9ha0vx0qwyn7p0cyqkm8qvweh5akjqjhlys34ys6gmm25jxrk3syearzr33qfk5czq8dxu04qq8njp0l3f0n6cuvyphqhtcjnaqg05vrgnhwzzmgm825383s' + + assert l1.rpc.decode(inv)['valid'] is True + subprocess.run(["devtools/bolt12-cli", "decode", inv], check=True)