Skip to content

Commit ccaf04d

Browse files
committed
invoice: add deschashonly parameter.
LNURL wants this so they can include images etc in descriptions. Replaces: #4892 Changelog-Added: JSON-RPC: `invoice` has a new parameter `deschashonly` to put hash of description in bolt11. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 290dfd2 commit ccaf04d

File tree

5 files changed

+55
-9
lines changed

5 files changed

+55
-9
lines changed

common/bolt11.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,11 +1119,16 @@ char *bolt11_encode_(const tal_t *ctx,
11191119
/* Thus we do built-in fields, then extras last. */
11201120
encode_p(&data, &b11->payment_hash);
11211121

1122-
if (b11->description)
1123-
encode_d(&data, b11->description);
1124-
1122+
/* BOLT #11:
1123+
* A writer:
1124+
*...
1125+
* - MUST include either exactly one `d` or exactly one `h` field.
1126+
*/
1127+
/* We sometimes keep description around (to put in db), so prefer hash */
11251128
if (b11->description_hash)
11261129
encode_h(&data, b11->description_hash);
1130+
else if (b11->description)
1131+
encode_d(&data, b11->description);
11271132

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

contrib/pyln-client/pyln/client/lightning.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ def help(self, command=None):
828828
return self.call("help", payload)
829829

830830
def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None,
831-
preimage=None, exposeprivatechannels=None, cltv=None):
831+
preimage=None, exposeprivatechannels=None, cltv=None, deschashonly=None):
832832
"""
833833
Create an invoice for {msatoshi} with {label} and {description} with
834834
optional {expiry} seconds (default 1 week).
@@ -842,6 +842,7 @@ def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None,
842842
"preimage": preimage,
843843
"exposeprivatechannels": exposeprivatechannels,
844844
"cltv": cltv,
845+
"deschashonly": deschashonly,
845846
}
846847
return self.call("invoice", payload)
847848

doc/lightning-invoice.7.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ SYNOPSIS
55
--------
66

77
**invoice** *msatoshi* *label* *description* [*expiry*]
8-
[*fallbacks*] [*preimage*] [*exposeprivatechannels*] [*cltv*]
8+
[*fallbacks*] [*preimage*] [*exposeprivatechannels*] [*cltv*] [*deschashonly*]
99

1010
DESCRIPTION
1111
-----------
@@ -29,8 +29,9 @@ of this invoice.
2929

3030
The *description* is a short description of purpose of payment, e.g. *1
3131
cup of coffee*. This value is encoded into the BOLT11 invoice and is
32-
viewable by any node you send this invoice to. It must be UTF-8, and
33-
cannot use *\\u* JSON escape codes.
32+
viewable by any node you send this invoice to (unless *deschashonly* is
33+
true as described below). It must be UTF-8, and cannot use *\\u* JSON
34+
escape codes.
3435

3536
The *expiry* is optionally the time the invoice is valid for; without a
3637
suffix it is interpreted as seconds, otherwise suffixes *s*, *m*, *h*,
@@ -68,6 +69,11 @@ payment.
6869
If specified, *cltv* sets the *min_final_cltv_expiry* for the invoice.
6970
Otherwise, it's set to the parameter **cltv-final**.
7071

72+
If *deschash* is true (default false), then the bolt11 returned
73+
contains a hash of the *description*, rather than the *description*
74+
itself: this allows much longer descriptions, but they must be
75+
communicated via some other mechanism.
76+
7177
RETURN VALUE
7278
------------
7379

lightningd/invoice.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ static struct command_result *json_invoice(struct command *cmd,
11351135
u32 *cltv;
11361136
struct jsonrpc_request *req;
11371137
struct plugin *plugin;
1138+
bool *hashonly;
11381139
#if DEVELOPER
11391140
const jsmntok_t *routes;
11401141
#endif
@@ -1153,6 +1154,7 @@ static struct command_result *json_invoice(struct command *cmd,
11531154
&info->chanhints),
11541155
p_opt_def("cltv", param_number, &cltv,
11551156
cmd->ld->config.cltv_final),
1157+
p_opt_def("deschashonly", param_bool, &hashonly, false),
11561158
#if DEVELOPER
11571159
p_opt("dev-routes", param_array, &routes),
11581160
#endif
@@ -1165,7 +1167,7 @@ static struct command_result *json_invoice(struct command *cmd,
11651167
INVOICE_MAX_LABEL_LEN);
11661168
}
11671169

1168-
if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT) {
1170+
if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) {
11691171
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
11701172
"Descriptions greater than %d bytes "
11711173
"not yet supported "
@@ -1207,7 +1209,18 @@ static struct command_result *json_invoice(struct command *cmd,
12071209
info->b11->min_final_cltv_expiry = *cltv;
12081210
info->b11->expiry = *expiry;
12091211
info->b11->description = tal_steal(info->b11, desc_val);
1210-
info->b11->description_hash = NULL;
1212+
/* BOLT #11:
1213+
* * `h` (23): `data_length` 52. 256-bit description of purpose of payment (SHA256).
1214+
*...
1215+
* A writer:
1216+
*...
1217+
* - MUST include either exactly one `d` or exactly one `h` field.
1218+
*/
1219+
if (*hashonly) {
1220+
info->b11->description_hash = tal(info->b11, struct sha256);
1221+
sha256(info->b11->description_hash, desc_val, strlen(desc_val));
1222+
} else
1223+
info->b11->description_hash = NULL;
12111224
info->b11->payment_secret = tal_dup(info->b11, struct secret,
12121225
&payment_secret);
12131226
info->b11->features = tal_dup_talarr(info->b11, u8,

tests/test_invoices.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,24 @@ def match(node, query, invoice):
712712
for q in queries:
713713
r = l1.rpc.listinvoices(**q)
714714
assert len(r['invoices']) == 0
715+
716+
717+
def test_invoice_deschash(node_factory, chainparams):
718+
l1, l2 = node_factory.line_graph(2)
719+
720+
# BOLT #11:
721+
# * `h`: tagged field: hash of description
722+
# * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52)
723+
# * `8yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs`: SHA256 of 'One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon'
724+
inv = l2.rpc.invoice(42, 'label', 'One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon', deschashonly=True)
725+
assert '8yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs' in inv['bolt11']
726+
727+
b11 = l2.rpc.decodepay(inv['bolt11'])
728+
assert 'description' not in b11
729+
assert b11['description_hash'] == '3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1'
730+
731+
listinv = only_one(l2.rpc.listinvoices()['invoices'])
732+
assert listinv['description'] == 'One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon'
733+
734+
# Make sure we can pay it!
735+
l1.rpc.pay(inv['bolt11'])

0 commit comments

Comments
 (0)