From 018ca05b25536502cdf9c11ca1213e563eb625d7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 14 Dec 2020 12:07:26 +1030 Subject: [PATCH] invoice: hack in merkle of invoice as "payment_secret" (EXPERIMENTAL_FEATURES) This lets actually pay the invoice that fetchinvoice returns. Signed-off-by: Rusty Russell --- lightningd/invoice.c | 30 +++++++++++++++++++ lightningd/test/run-invoice-select-inchan.c | 7 +++++ tests/test_pay.py | 33 +++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/lightningd/invoice.c b/lightningd/invoice.c index dd84c325774a..0bab357262ff 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -135,6 +135,31 @@ static void invoice_secret(const struct preimage *payment_preimage, memcpy(payment_secret->data, secret.u.u8, sizeof(secret.u.u8)); } +#if EXPERIMENTAL_FEATURES +/* FIXME: This is a hack. The real secret should be a signature of some + * onion key, using the payer_id */ +static void invoice_secret_bolt12(struct lightningd *ld, + const char *invstring, + struct secret *payment_secret) +{ + char *fail; + struct tlv_invoice *inv; + struct sha256 merkle; + + inv = invoice_decode(tmpctx, invstring, strlen(invstring), + NULL, NULL, &fail); + if (!inv) { + log_broken(ld->log, "Unable to decode our invoice %s", + invstring); + return; + } + + merkle_tlv(inv->fields, &merkle); + BUILD_ASSERT(sizeof(*payment_secret) == sizeof(merkle)); + memcpy(payment_secret, &merkle, sizeof(merkle)); +} +#endif /* EXPERIMENTAL_FEATURES */ + struct invoice_payment_hook_payload { struct lightningd *ld; /* Set to NULL if it is deleted while waiting for plugin */ @@ -348,6 +373,11 @@ invoice_check_payment(const tal_t *ctx, if (payment_secret) { struct secret expected; +#if EXPERIMENTAL_FEATURES + if (details->invstring && strstarts(details->invstring, "lni1")) + invoice_secret_bolt12(ld, details->invstring, &expected); + else +#endif /* EXPERIMENTAL_FEATURES */ invoice_secret(&details->r, &expected); if (!secret_eq_consttime(payment_secret, &expected)) { log_debug(ld->log, "Attept to pay %s with wrong secret", diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 4e50c776a2cc..ffd20d57ea55 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -760,6 +760,13 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED, /* Generated stub for invoice_encode */ char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) { fprintf(stderr, "invoice_encode called!\n"); abort(); } +/* Generated stub for invoice_decode */ +struct tlv_invoice *invoice_decode(const tal_t *ctx UNNEEDED, + const char *b12 UNNEEDED, size_t b12len UNNEEDED, + const struct feature_set *our_features UNNEEDED, + const struct chainparams *must_be_chain UNNEEDED, + char **fail UNNEEDED) +{ fprintf(stderr, "invoice_decode called!\n"); abort(); } #endif static void add_candidate(struct routehint_candidate **candidates, int n, diff --git a/tests/test_pay.py b/tests/test_pay.py index a0c14340223a..4881cd93f466 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3856,6 +3856,8 @@ def test_fetchinvoice(node_factory, bitcoind): inv1 = l1.rpc.call('fetchinvoice', {'offer': offer}) inv2 = l1.rpc.call('fetchinvoice', {'offer': offer}) assert inv1 != inv2 + l1.rpc.pay(inv1['invoice']) + l1.rpc.pay(inv2['invoice']) # Single-use invoice can be fetched multiple times, only paid once. offer = l3.rpc.call('offer', {'amount': '1msat', @@ -3866,3 +3868,34 @@ def test_fetchinvoice(node_factory, bitcoind): inv1 = l1.rpc.call('fetchinvoice', {'offer': offer}) inv2 = l1.rpc.call('fetchinvoice', {'offer': offer}) assert inv1 != inv2 + + l1.rpc.pay(inv1['invoice']) + + # FIXME: We don't report failure yet. +# # We can't pay the other one now. +# with pytest.raises(RpcError, match='???'): +# l1.rpc.pay(inv2['invoice']) +# +# # We can't reuse the offer, either. +# with pytest.raises(RpcError, match='???'): +# l1.rpc.call('fetchinvoice', {'offer': offer}) + + # Recurring offer. + offer = l2.rpc.call('offer', {'amount': '1msat', + 'description': 'recurring test', + 'recurrence': '1minutes'})['bolt12'] + print(offer) + + ret = l1.rpc.call('fetchinvoice', {'offer': offer, + 'recurrence_counter': 0, + 'recurrence_label': 'test recurrence'}) + print(ret) + + l1.rpc.pay(ret['invoice'], label='test recurrence') + + ret = l1.rpc.call('fetchinvoice', {'offer': offer, + 'recurrence_counter': 1, + 'recurrence_label': 'test recurrence'}) + print(ret) + + l1.rpc.pay(ret['invoice'], label='test recurrence')