Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bolt12: allow to inject payer_metadata #7786

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,7 @@
"FetchinvoiceRequest": {
"FetchInvoice.amount_msat": 2,
"FetchInvoice.offer": 1,
"FetchInvoice.payer_metadata": 9,
"FetchInvoice.payer_note": 8,
"FetchInvoice.quantity": 3,
"FetchInvoice.recurrence_counter": 4,
Expand Down Expand Up @@ -5609,6 +5610,10 @@
"added": "pre-v0.10.1",
"deprecated": null
},
"FetchInvoice.payer_metadata": {
"added": "v24.11",
"deprecated": null
},
"FetchInvoice.payer_note": {
"added": "pre-v0.10.1",
"deprecated": null
Expand Down
1 change: 1 addition & 0 deletions cln-grpc/proto/node.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-grpc/src/convert.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-rpc/src/model.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13425,6 +13425,13 @@
"To ask the issuer to include in the fetched invoice."
]
},
"payer_metadata": {
"type": "string",
"description": [
"Derive the payer_id from the specified payer_metadata. Please be sure that `payer_metdata` can not be derived by anyone, so put some secret into it."
],
"added": "v24.11"
},
"dev_reply_path": {
"hidden": true
},
Expand Down
1,052 changes: 526 additions & 526 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions doc/schemas/lightning-fetchinvoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
"To ask the issuer to include in the fetched invoice."
]
},
"payer_metadata": {
"type": "string",
"description": [
"Derive the payer_id from the specified payer_metadata. Please be sure that `payer_metdata` can not be derived by anyone, so put some secret into it."
],
"added": "v24.11"
},
"dev_reply_path": {
"hidden": true
},
Expand Down
1 change: 1 addition & 0 deletions lightningd/offer.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ static struct command_result *prev_payment(struct command *cmd,
" label and offer");
}

/* FIXME(vincenzopalazzo): move this to comm/bolt12.h */
static struct command_result *param_b12_invreq(struct command *cmd,
const char *name,
const char *buffer,
Expand Down
51 changes: 33 additions & 18 deletions plugins/fetchinvoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -819,24 +819,26 @@ struct command_result *json_fetchinvoice(struct command *cmd,
{
struct amount_msat *msat;
const char *rec_label, *payer_note;
u8 *payer_metadata;
struct out_req *req;
struct tlv_invoice_request *invreq;
struct sent *sent = tal(cmd, struct sent);
u32 *timeout;
u64 *quantity;
u32 *recurrence_counter, *recurrence_start;

if (!param(cmd, buffer, params,
p_req("offer", param_offer, &sent->offer),
p_opt("amount_msat", param_msat, &msat),
p_opt("quantity", param_u64, &quantity),
p_opt("recurrence_counter", param_number, &recurrence_counter),
p_opt("recurrence_start", param_number, &recurrence_start),
p_opt("recurrence_label", param_string, &rec_label),
p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note),
p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd),
p_opt("dev_reply_path", param_dev_reply_path, &sent->dev_reply_path),
if (!param_check(cmd, buffer, params,
vincenzopalazzo marked this conversation as resolved.
Show resolved Hide resolved
p_req("offer", param_offer, &sent->offer),
p_opt("amount_msat", param_msat, &msat),
p_opt("quantity", param_u64, &quantity),
p_opt("recurrence_counter", param_number, &recurrence_counter),
p_opt("recurrence_start", param_number, &recurrence_start),
p_opt("recurrence_label", param_string, &rec_label),
p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note),
p_opt("payer_metadata", param_bin_from_hex, &payer_metadata),
p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd),
p_opt("dev_reply_path", param_dev_reply_path, &sent->dev_reply_path),
NULL))
return command_param_failed();

Expand Down Expand Up @@ -938,6 +940,10 @@ struct command_result *json_fetchinvoice(struct command *cmd,
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"needs recurrence_counter");

if (payer_metadata)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot set payer_metadata for recurring offers");

/* BOLT-offers-recurrence #12:
* - if the offer contained `recurrence_base` with
* `start_any_period` non-zero:
Expand Down Expand Up @@ -998,13 +1004,18 @@ struct command_result *json_fetchinvoice(struct command *cmd,
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unnecessary recurrence_start");

/* BOLT-offers #12:
* - MUST set `invreq_metadata` to an unpredictable series of
* bytes.
*/
invreq->invreq_metadata = tal_arr(invreq, u8, 16);
randombytes_buf(invreq->invreq_metadata,
tal_bytelen(invreq->invreq_metadata));
/* if the payer force to use the payer_metadata */
if (payer_metadata) {
invreq->invreq_metadata = tal_steal(invreq, payer_metadata);
} else {
/* BOLT-offers #12:
* - MUST set `invreq_metadata` to an unpredictable series of
* bytes.
*/
invreq->invreq_metadata = tal_arr(invreq, u8, 16);
randombytes_buf(invreq->invreq_metadata,
tal_bytelen(invreq->invreq_metadata));
}
}

/* We derive transient payer_id from invreq_metadata */
Expand Down Expand Up @@ -1045,6 +1056,10 @@ struct command_result *json_fetchinvoice(struct command *cmd,
strlen(payer_note),
0);

/* If only checking, we're done now */
if (command_check_only(cmd))
return command_check_done(cmd);

/* Make the invoice request (fills in payer_key and payer_info) */
req = jsonrpc_request_start(cmd, "createinvoicerequest",
&invreq_done,
Expand Down
31 changes: 31 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -6702,3 +6702,34 @@ def get_local_channel_by_id(node, chanid):
receipt = only_one(l3.rpc.listinvoices("inv")["invoices"])
assert receipt["status"] == "paid"
assert receipt["amount_received_msat"] == total_msat


def test_fetchinvoice_with_payer_metadata(node_factory, bitcoind):
# We remove the conversion plugin on l3, causing it to get upset.
l1, l2 = node_factory.line_graph(2, wait_for_announce=True,
opts=[{'experimental-offers': None},
{'experimental-offers': None}])

# Simple default offer first.
offer = l2.rpc.call('offer', {'amount': 'any'})
assert offer['created'] is True

# Fetch an invoice for a monthly payroll for two different people, one is macros and the other is vincenzopalazzo.
# Now the payroll software has a bug and injects the wrong payer_node description (this often happens with wire descriptions).
inv1 = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12'], 'amount_msat': '1sat', 'payer_note': 'Payment For vincenzopalazzo', 'payer_metadata': b'macros'.hex()})['invoice']
inv2 = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12'], 'amount_msat': '1sat', 'payer_note': 'Payment For vincenzopalazzo', 'payer_metadata': b'vincenzopalazzo'.hex()})['invoice']
assert inv1 != inv2
# At this point, macros noticed the bug in the payroll system and reported a
# 'not paid for the current month' issue. The payroll system can verify that
# there is a bug in the fetchinvoice, but it can also prove that the payment was sent
# to macros because the payroll system can generate the `payer_id` with the `macros` metadata.
decode1 = l1.rpc.call('decode', {'string': inv1})
decode2 = l1.rpc.call('decode', {'string': inv2})
assert decode1['invreq_payer_id'] != decode2['invreq_payer_id']

# Delay to avoid sending too many onion messages per second!
time.sleep(1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch thanks!


inv3 = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12'], 'amount_msat': '1sat', 'payer_note': 'Payment For vincenzopalazzo', 'payer_metadata': b'macros'.hex()})['invoice']
decode3 = l1.rpc.call('decode', {'string': inv3})
assert decode1['invreq_payer_id'] == decode3['invreq_payer_id']
Loading