From e01abf0b34182b54d86494169f72e313442e2925 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 31 Mar 2022 19:40:50 +1030 Subject: [PATCH] bolt11: support payment_metadata. Signed-off-by: Rusty Russell --- Makefile | 2 +- common/bolt11.c | 53 +++++++++++++++++++++++++-- common/bolt11.h | 3 ++ common/bolt11_json.c | 2 + common/features.c | 10 ++++- common/features.h | 2 + common/test/run-bolt11.c | 46 +++++++++++++++++++++++ devtools/bolt11-cli.c | 4 ++ doc/lightning-decode.7.md | 3 +- doc/lightning-decodepay.7.md | 3 +- doc/schemas/decode.schema.json | 6 +++ doc/schemas/decodepay.schema.json | 4 ++ lightningd/lightningd.c | 1 + tests/test_pay.py | 6 +++ wire/extracted_onion_01_offers.patch | 4 +- wire/extracted_onion_exp_enctlv.patch | 4 +- wire/onion_wire.csv | 2 + 17 files changed, 145 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index a61d2b684019..a835e78d8e54 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ CCANDIR := ccan # Where we keep the BOLT RFCs BOLTDIR := ../lightning-rfc/ -DEFAULT_BOLTVERSION := 93909f67f6a48ee3f155a6224c182e612dd5f187 +DEFAULT_BOLTVERSION := f6c4d7604150986894bcb46d67c5c88680740b12 # Can be overridden on cmdline. BOLTVERSION := $(DEFAULT_BOLTVERSION) diff --git a/common/bolt11.c b/common/bolt11.c index b33ac132ec1c..a4cc1df49b04 100644 --- a/common/bolt11.c +++ b/common/bolt11.c @@ -516,6 +516,33 @@ static char *decode_9(struct bolt11 *b11, return NULL; } +/* BOLT #11: + * + * `m` (27): `data_length` variable. Additional metadata to attach to + * the payment. Note that the size of this field is limited by the + * maximum hop payload size. Long metadata fields reduce the maximum + * route length. + */ +static char *decode_m(struct bolt11 *b11, + struct hash_u5 *hu5, + u5 **data, size_t *data_len, + size_t data_length, + bool *have_m) +{ + size_t mlen = (data_length * 5) / 8; + + if (*have_m) + return unknown_field(b11, hu5, data, data_len, 'm', + data_length); + + b11->metadata = tal_arr(b11, u8, mlen); + pull_bits_certain(hu5, data, data_len, b11->metadata, + data_length * 5, false); + + *have_m = true; + return NULL; +} + struct bolt11 *new_bolt11(const tal_t *ctx, const struct amount_msat *msat TAKES) { @@ -535,6 +562,7 @@ struct bolt11 *new_bolt11(const tal_t *ctx, */ b11->min_final_cltv_expiry = 18; b11->payment_secret = NULL; + b11->metadata = NULL; if (msat) b11->msat = tal_dup(b11, struct amount_msat, msat); @@ -557,7 +585,7 @@ struct bolt11 *bolt11_decode_nosig(const tal_t *ctx, const char *str, struct bolt11 *b11 = new_bolt11(ctx, NULL); struct hash_u5 hu5; bool have_p = false, have_d = false, have_h = false, - have_x = false, have_c = false, have_s = false; + have_x = false, have_c = false, have_s = false, have_m = false; *have_n = false; b11->routes = tal_arr(b11, struct route_info *, 0); @@ -760,6 +788,10 @@ struct bolt11 *bolt11_decode_nosig(const tal_t *ctx, const char *str, problem = decode_s(b11, &hu5, &data, &data_len, data_length, &have_s); break; + case 'm': + problem = decode_m(b11, &hu5, &data, &data_len, + data_length, &have_m); + break; default: unknown_field(b11, &hu5, &data, &data_len, bech32_charset[type], data_length); @@ -936,6 +968,11 @@ static void encode_p(u5 **data, const struct sha256 *hash) push_field(data, 'p', hash, 256); } +static void encode_m(u5 **data, const u8 *metadata) +{ + push_field(data, 'm', metadata, tal_bytelen(metadata) * CHAR_BIT); +} + static void encode_d(u5 **data, const char *description) { push_field(data, 'd', description, strlen(description) * CHAR_BIT); @@ -1011,13 +1048,20 @@ static void encode_r(u5 **data, const struct route_info *r) tal_free(rinfo); } -static void maybe_encode_9(u5 **data, const u8 *features) +static void maybe_encode_9(u5 **data, const u8 *features, + bool have_payment_metadata) { u5 *f5 = tal_arr(NULL, u5, 0); for (size_t i = 0; i < tal_count(features) * CHAR_BIT; i++) { if (!feature_is_set(features, i)) continue; + + /* Don't set option_payment_metadata unless we acually use it */ + if (!have_payment_metadata + && COMPULSORY_FEATURE(i) == OPT_PAYMENT_METADATA) + continue; + /* We expand it out so it makes a BE 5-bit/btye bitfield */ set_feature_bit(&f5, (i / 5) * 8 + (i % 5)); } @@ -1136,6 +1180,9 @@ char *bolt11_encode_(const tal_t *ctx, 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`). */ @@ -1150,7 +1197,7 @@ char *bolt11_encode_(const tal_t *ctx, for (size_t i = 0; i < tal_count(b11->routes); i++) encode_r(&data, b11->routes[i]); - maybe_encode_9(&data, b11->features); + maybe_encode_9(&data, b11->features, b11->metadata != NULL); list_for_each(&b11->extra_fields, extra, list) if (!encode_extra(&data, extra)) diff --git a/common/bolt11.h b/common/bolt11.h index 3dea30b82f35..19b87f78fc8c 100644 --- a/common/bolt11.h +++ b/common/bolt11.h @@ -76,6 +76,9 @@ struct bolt11 { /* Features bitmap, if any. */ u8 *features; + /* Optional metadata to send with payment. */ + u8 *metadata; + struct list_head extra_fields; }; diff --git a/common/bolt11_json.c b/common/bolt11_json.c index 4fc1dacec925..b2280c47d157 100644 --- a/common/bolt11_json.c +++ b/common/bolt11_json.c @@ -66,6 +66,8 @@ void json_add_bolt11(struct json_stream *response, b11->payment_secret); if (b11->features) json_add_hex_talarr(response, "features", b11->features); + if (b11->metadata) + json_add_hex_talarr(response, "payment_metadata", b11->metadata); if (tal_count(b11->fallbacks)) { json_array_start(response, "fallbacks"); for (size_t i = 0; i < tal_count(b11->fallbacks); i++) diff --git a/common/features.c b/common/features.c index 777afbe3c8d7..e95d631e8964 100644 --- a/common/features.c +++ b/common/features.c @@ -97,6 +97,14 @@ static const struct feature_style feature_styles[] = { .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, + { OPT_PAYMENT_METADATA, + .copy_style = { [INIT_FEATURE] = FEATURE_DONT_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_DONT_REPRESENT, + /* Note: we don't actually set this in invoices, since + * we don't need to use it, but if we don't set it here + * we refuse to parse it. */ + [BOLT11_FEATURE] = FEATURE_REPRESENT, + [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, }; struct dependency { @@ -416,7 +424,7 @@ const char *feature_name(const tal_t *ctx, size_t f) "option_provide_peer_backup", /* https://github.com/lightningnetwork/lightning-rfc/pull/881 */ NULL, NULL, - NULL, + "option_payment_metadata", NULL, /* 50/51 */ NULL, "option_keysend", diff --git a/common/features.h b/common/features.h index 1aa8ca82994e..3bea463565c8 100644 --- a/common/features.h +++ b/common/features.h @@ -119,6 +119,7 @@ const char *fmt_featurebits(const tal_t *ctx, const u8 *featurebits); * | 20/21 | `option_anchor_outputs` |... IN ... * | 22/23 | `option_anchors_zero_fee_htlc_tx` |... IN ... * | 26/27 | `option_shutdown_anysegwit` |... IN ... + * | 48/49 | `option_payment_metadata` |... 9 ... */ #define OPT_PAYMENT_SECRET 14 #define OPT_BASIC_MPP 16 @@ -126,6 +127,7 @@ const char *fmt_featurebits(const tal_t *ctx, const u8 *featurebits); #define OPT_ANCHOR_OUTPUTS 20 #define OPT_ANCHORS_ZERO_FEE_HTLC_TX 22 #define OPT_SHUTDOWN_ANYSEGWIT 26 +#define OPT_PAYMENT_METADATA 48 /* BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: * | 28/29 | `option_dual_fund` | ... IN9 ... diff --git a/common/test/run-bolt11.c b/common/test/run-bolt11.c index 4ed5e8369fe3..488b848cdd62 100644 --- a/common/test/run-bolt11.c +++ b/common/test/run-bolt11.c @@ -564,6 +564,52 @@ int main(int argc, char *argv[]) memset(b11->payment_secret, 0x11, sizeof(*b11->payment_secret)); test_b11("lnbc9678785340p1pwmna7lpp5gc3xfm08u9qy06djf8dfflhugl6p7lgza6dsjxq454gxhj9t7a0sd8dgfkx7cmtwd68yetpd5s9xar0wfjn5gpc8qhrsdfq24f5ggrxdaezqsnvda3kkum5wfjkzmfqf3jkgem9wgsyuctwdus9xgrcyqcjcgpzgfskx6eqf9hzqnteypzxz7fzypfhg6trddjhygrcyqezcgpzfysywmm5ypxxjemgw3hxjmn8yptk7untd9hxwg3q2d6xjcmtv4ezq7pqxgsxzmnyyqcjqmt0wfjjq6t5v4khxsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsxqyjw5qcqp2rzjq0gxwkzc8w6323m55m4jyxcjwmy7stt9hwkwe2qxmy8zpsgg7jcuwz87fcqqeuqqqyqqqqlgqqqqn3qq9q9qrsgqrvgkpnmps664wgkp43l22qsgdw4ve24aca4nymnxddlnp8vh9v2sdxlu5ywdxefsfvm0fq3sesf08uf6q9a2ke0hc9j6z6wlxg5z5kqpu2v9wz", b11, NULL); + /* BOLT #11: + * > ### Please send 0.01 BTC with payment metadata 0x01fafaf0 + * > lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc + * + * Breakdown: + * + * * `lnbc`: prefix, Lightning on Bitcoin mainnet + * * `10m`: amount (10 milli-bitcoin) + * * `1`: Bech32 separator + * * `pvjluez`: timestamp (1496314658) + * * `p`: payment hash + * * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52) + * * `qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq`: payment hash 0001020304050607080900010203040506070809000102030405060708090102 + * * `d`: short description + * * `p9`: `data_length` (`p` = 1, `9` = 5; 1 * 32 + 5 == 37) + * * `wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2`: 'payment metadata inside' + * * `m`: metadata + * * `q8`: `data_length` (`q` = 0, `8` = 7; 0 * 32 + 7 == 7) + * * `q8a04uq`: 0x01fafaf0 + * * `s`: payment secret + * * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52) + * * `zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs`: 0x1111111111111111111111111111111111111111111111111111111111111111 + * * `9`: features + * * `q2`: `data_length` (`q` = 0, `2` = 10; 0 * 32 + 10 == 10) + * * `gqqqqqqsgq`: [b01000000000000000000000000000000000100000100000000] = 8 + 14 + 48 + * * `7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqp`: signature + * * `d05wjc`: Bech32 checksum + */ + msatoshi = AMOUNT_MSAT(1000000000); + b11 = new_bolt11(tmpctx, &msatoshi); + b11->chain = chainparams_for_network("bitcoin"); + b11->timestamp = 1496314658; + if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102", + strlen("0001020304050607080900010203040506070809000102030405060708090102"), + &b11->payment_hash, sizeof(b11->payment_hash))) + abort(); + b11->receiver_id = node; + b11->description = "payment metadata inside"; + b11->metadata = tal_hexdata(b11, "01fafaf0", strlen("01fafaf0")); + set_feature_bit(&b11->features, 8); + set_feature_bit(&b11->features, 14); + set_feature_bit(&b11->features, 48); + b11->payment_secret = tal(b11, struct secret); + memset(b11->payment_secret, 0x11, sizeof(*b11->payment_secret)); + test_b11("lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc", b11, NULL); + /* BOLT #11: * * > ### Bech32 checksum is invalid. diff --git a/devtools/bolt11-cli.c b/devtools/bolt11-cli.c index cb644baab72e..018d0e8af7f8 100644 --- a/devtools/bolt11-cli.c +++ b/devtools/bolt11-cli.c @@ -163,6 +163,10 @@ int main(int argc, char *argv[]) printf("\n"); } + if (b11->metadata) + printf("metadata: %s\n", + tal_hex(ctx, b11->metadata)); + list_for_each(&b11->extra_fields, extra, list) { char *data = tal_arr(ctx, char, tal_count(extra->data)+1); diff --git a/doc/lightning-decode.7.md b/doc/lightning-decode.7.md index 81d8a62b9453..de4dc749d058 100644 --- a/doc/lightning-decode.7.md +++ b/doc/lightning-decode.7.md @@ -143,6 +143,7 @@ If **type** is "bolt11 invoice", and **valid** is *true*: - **description_hash** (hex, optional): the hash of the description, in place of *description* (always 64 characters) - **payment_secret** (hex, optional): the secret to hand to the payee node (always 64 characters) - **features** (hex, optional): the features bitmap for this invoice + - **payment_metadata** (hex, optional): the payment_metadata to put in the payment - **fallbacks** (array of objects, optional): onchain addresses: - **type** (string): the address type (if known) (one of "P2PKH", "P2SH", "P2WPKH", "P2WSH") - **hex** (hex): Raw encoded address @@ -180,4 +181,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:d05b5fc1bf230b3bbd03e2023fb0c6bbefb700f7c3cfb43512da48dbce45f005) +[comment]: # ( SHA256STAMP:6ccf1b9195e64f897f65198a81c47bbd7e16387e4bdc74d624e2ec04a24e9873) diff --git a/doc/lightning-decodepay.7.md b/doc/lightning-decodepay.7.md index bbdc75c04892..300d80451737 100644 --- a/doc/lightning-decodepay.7.md +++ b/doc/lightning-decodepay.7.md @@ -29,6 +29,7 @@ On success, an object is returned, containing: - **description_hash** (hex, optional): the hash of the description, in place of *description* (always 64 characters) - **payment_secret** (hex, optional): the secret to hand to the payee node (always 64 characters) - **features** (hex, optional): the features bitmap for this invoice +- **payment_metadata** (hex, optional): the payment_metadata to put in the payment - **fallbacks** (array of objects, optional): onchain addresses: - **type** (string): the address type (if known) (one of "P2PKH", "P2SH", "P2WPKH", "P2WSH") - **hex** (hex): Raw encoded address @@ -69,4 +70,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:d92e1197708fff40f8ad71ccec3c0d8122d8088da1803c02bb042b09dbf2ee33) +[comment]: # ( SHA256STAMP:17cb6c66c75e907f3a2583d702aec2fc6e5a7b6026d05a3ed9957304799c9aef) diff --git a/doc/schemas/decode.schema.json b/doc/schemas/decode.schema.json index 808ef8f4313d..2c84e820bd54 100644 --- a/doc/schemas/decode.schema.json +++ b/doc/schemas/decode.schema.json @@ -746,6 +746,8 @@ ], "additionalProperties": false, "properties": { + "type": {}, + "valid": {}, "currency": { "type": "string", "description": "the BIP173 name for the currency" @@ -804,6 +806,10 @@ "type": "hex", "description": "the features bitmap for this invoice" }, + "payment_metadata": { + "type": "hex", + "description": "the payment_metadata to put in the payment" + }, "fallbacks": { "type": "array", "description": "onchain addresses", diff --git a/doc/schemas/decodepay.schema.json b/doc/schemas/decodepay.schema.json index 15fd12298657..0823f7afa454 100644 --- a/doc/schemas/decodepay.schema.json +++ b/doc/schemas/decodepay.schema.json @@ -70,6 +70,10 @@ "type": "hex", "description": "the features bitmap for this invoice" }, + "payment_metadata": { + "type": "hex", + "description": "the payment_metadata to put in the payment" + }, "fallbacks": { "type": "array", "description": "onchain addresses", diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index c55e861d2a2b..bb4a1ac44ad1 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -823,6 +823,7 @@ static struct feature_set *default_features(const tal_t *ctx) OPTIONAL_FEATURE(OPT_GOSSIP_QUERIES_EX), OPTIONAL_FEATURE(OPT_STATIC_REMOTEKEY), OPTIONAL_FEATURE(OPT_SHUTDOWN_ANYSEGWIT), + OPTIONAL_FEATURE(OPT_PAYMENT_METADATA), #if EXPERIMENTAL_FEATURES OPTIONAL_FEATURE(OPT_ANCHOR_OUTPUTS), OPTIONAL_FEATURE(OPT_QUIESCE), diff --git a/tests/test_pay.py b/tests/test_pay.py index 96a65f9716b1..f8682643c036 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5236,3 +5236,9 @@ def test_pay_manual_exclude(node_factory, bitcoind): # Exclude direct channel id with pytest.raises(RpcError, match=r'is not reachable directly and all routehints were unusable.'): l2.rpc.pay(inv, exclude=[scid23]) + + +def test_pay_bolt11_metadata(node_factory, bitcoind): + l1 = node_factory.get_node() + + l1.rpc.decode('lnbcrt10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqcqpjsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgqrk6hdutpaetmm3afjn0vfczgeyv0cy739rr939kwd4h5j3khxcskhgf59eaqy8wyq82tsnaqc5y32ed4jg34jw7rmeva9u6kfhymawgptmy5f6') diff --git a/wire/extracted_onion_01_offers.patch b/wire/extracted_onion_01_offers.patch index 90c0cb6c41ac..898c1373df3d 100644 --- a/wire/extracted_onion_01_offers.patch +++ b/wire/extracted_onion_01_offers.patch @@ -1,9 +1,9 @@ --- wire/extracted_onion_wire_csv 2020-03-25 10:24:12.861645774 +1030 +++ - 2020-03-26 13:47:13.498294435 +1030 @@ -8,6 +8,30 @@ - tlvtype,tlv_payload,payment_data,8 - tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,tlv_payload,payment_data,total_msat,tu64, + tlvtype,tlv_payload,payment_metadata,16 + tlvdata,tlv_payload,payment_metadata,payment_metadata,byte,... +tlvtype,obs2_onionmsg_payload,reply_path,2 +tlvdata,obs2_onionmsg_payload,reply_path,first_node_id,point, +tlvdata,obs2_onionmsg_payload,reply_path,blinding,point, diff --git a/wire/extracted_onion_exp_enctlv.patch b/wire/extracted_onion_exp_enctlv.patch index 1897193b8c8b..da51d82268bc 100644 --- a/wire/extracted_onion_exp_enctlv.patch +++ b/wire/extracted_onion_exp_enctlv.patch @@ -2,10 +2,12 @@ +++ - 2020-03-20 15:11:55.763880895 +1030 --- wire/onion_exp_wire.csv.~1~ 2021-11-17 10:56:59.947630815 +1030 +++ wire/onion_exp_wire.csv 2021-11-17 10:59:39.304581244 +1030 -@@ -8,6 +8,10 @@ +@@ -8,8 +8,12 @@ tlvtype,tlv_payload,payment_data,8 tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,tlv_payload,payment_data,total_msat,tu64, + tlvtype,tlv_payload,payment_metadata,16 + tlvdata,tlv_payload,payment_metadata,payment_metadata,byte,... +tlvtype,tlv_payload,encrypted_recipient_data,10 +tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... +tlvtype,tlv_payload,blinding_point,12 diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv index 28c58724d8b5..9f255bdaf017 100644 --- a/wire/onion_wire.csv +++ b/wire/onion_wire.csv @@ -8,6 +8,8 @@ tlvdata,tlv_payload,short_channel_id,short_channel_id,short_channel_id, tlvtype,tlv_payload,payment_data,8 tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,tlv_payload,payment_data,total_msat,tu64, +tlvtype,tlv_payload,payment_metadata,16 +tlvdata,tlv_payload,payment_metadata,payment_metadata,byte,... tlvtype,obs2_onionmsg_payload,reply_path,2 tlvdata,obs2_onionmsg_payload,reply_path,first_node_id,point, tlvdata,obs2_onionmsg_payload,reply_path,blinding,point,