Skip to content

Commit

Permalink
bolt11: support payment_metadata.
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Apr 1, 2022
1 parent 2526e80 commit e01abf0
Show file tree
Hide file tree
Showing 17 changed files with 145 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
53 changes: 50 additions & 3 deletions common/bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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`).
*/
Expand All @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions common/bolt11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
2 changes: 2 additions & 0 deletions common/bolt11_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand Down
10 changes: 9 additions & 1 deletion common/features.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ 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
#define OPT_LARGE_CHANNELS 18
#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 ...
Expand Down
46 changes: 46 additions & 0 deletions common/test/run-bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions devtools/bolt11-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion doc/lightning-decode.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -180,4 +181,4 @@ RESOURCES

Main web site: <https://github.com/ElementsProject/lightning>

[comment]: # ( SHA256STAMP:d05b5fc1bf230b3bbd03e2023fb0c6bbefb700f7c3cfb43512da48dbce45f005)
[comment]: # ( SHA256STAMP:6ccf1b9195e64f897f65198a81c47bbd7e16387e4bdc74d624e2ec04a24e9873)
3 changes: 2 additions & 1 deletion doc/lightning-decodepay.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,4 +70,4 @@ RESOURCES

Main web site: <https://github.com/ElementsProject/lightning>

[comment]: # ( SHA256STAMP:d92e1197708fff40f8ad71ccec3c0d8122d8088da1803c02bb042b09dbf2ee33)
[comment]: # ( SHA256STAMP:17cb6c66c75e907f3a2583d702aec2fc6e5a7b6026d05a3ed9957304799c9aef)
6 changes: 6 additions & 0 deletions doc/schemas/decode.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,8 @@
],
"additionalProperties": false,
"properties": {
"type": {},
"valid": {},
"currency": {
"type": "string",
"description": "the BIP173 name for the currency"
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions doc/schemas/decodepay.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
4 changes: 2 additions & 2 deletions wire/extracted_onion_01_offers.patch
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
4 changes: 3 additions & 1 deletion wire/extracted_onion_exp_enctlv.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions wire/onion_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e01abf0

Please sign in to comment.