diff --git a/lightningd/offer.c b/lightningd/offer.c index f6f751ce4..f23a210ed 100644 --- a/lightningd/offer.c +++ b/lightningd/offer.c @@ -206,3 +206,224 @@ static const struct json_command disableoffer_command = { "Disable offer {offer_id}", }; AUTODATA(json_command, &disableoffer_command); + +/* We do some sanity checks now, since we're looking up prev payment anyway, + * but our main purpose is to fill in invreq->payer_info tweak. */ +static struct command_result *prev_payment(struct command *cmd, + const char *label, + struct tlv_invoice_request *invreq) +{ + const struct wallet_payment **payments; + bool prev_paid = false; + + assert(!invreq->payer_info); + payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL); + + for (size_t i = 0; i < tal_count(payments); i++) { + const struct tlv_invoice *inv; + char *fail; + + /* FIXME: Restrict db queries instead */ + if (!payments[i]->label || !streq(label, payments[i]->label)) + continue; + + if (!payments[i]->invstring) + continue; + + inv = invoice_decode(tmpctx, payments[i]->invstring, + strlen(payments[i]->invstring), + NULL, chainparams, &fail); + if (!inv) + continue; + + /* They can reuse labels across different offers. */ + if (!sha256_eq(inv->offer_id, invreq->offer_id)) + continue; + + /* Be paranoid, in case someone inserts their own + * clashing label! */ + if (!inv->recurrence_counter) + continue; + + /* BOLT-offers #12: + * - if the offer contained `recurrence_base` with + * `start_any_period` non-zero: + * - MUST include `recurrence_start` + * - MUST set `period_offset` to the period the sender wants + * for the initial request + * - MUST set `period_offset` to the same value on all + * following requests. + */ + if (invreq->recurrence_start) { + if (!inv->recurrence_start) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "unexpected" + " recurrence_start"); + if (*inv->recurrence_start != *invreq->recurrence_start) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "recurrence_start was" + " previously %u", + *inv->recurrence_start); + } else { + if (inv->recurrence_start) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "missing" + " recurrence_start"); + } + + if (*inv->recurrence_counter == *invreq->recurrence_counter-1) { + if (payments[i]->status == PAYMENT_COMPLETE) + prev_paid = true; + } + + if (inv->payer_info) + invreq->payer_info + = tal_dup_talarr(invreq, u8, inv->payer_info); + } + + if (!invreq->payer_info) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "No previous payment attempted for this" + " label and offer"); + + if (!prev_paid) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "previous invoice has not been paid"); + + return NULL; +} + +static struct command_result *param_b12_invreq(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct tlv_invoice_request **invreq) +{ + char *fail; + + *invreq = invrequest_decode(cmd, buffer + tok->start, + tok->end - tok->start, + cmd->ld->our_features, chainparams, &fail); + if (!*invreq) + return command_fail_badparam(cmd, name, buffer, tok, fail); + if ((*invreq)->payer_info) + return command_fail_badparam(cmd, name, buffer, tok, + "must not have payer_info"); + if ((*invreq)->payer_key) + return command_fail_badparam(cmd, name, buffer, tok, + "must not have payer_key"); + return NULL; +} + +static struct command_result *json_createinvoicerequest(struct command *cmd, + const char *buffer, + const jsmntok_t *obj, + const jsmntok_t *params) +{ + struct tlv_invoice_request *invreq; + const char *label; + struct sha256 tweakhash; + struct json_stream *response; + secp256k1_pubkey tweaked; + + if (!param(cmd, buffer, params, + p_req("bolt12", param_b12_invreq, &invreq), + p_opt("recurrence_label", param_escaped_string, &label), + NULL)) + return command_param_failed(); + + if (invreq->recurrence_counter) { + if (!label) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Need payment label for recurring payments"); + + if (*invreq->recurrence_counter != 0) { + struct command_result *err + = prev_payment(cmd, label, invreq); + if (err) + return err; + } + } + + if (!invreq->payer_info) { + /* BOLT-offers #12: + * `payer_info` might typically contain information about the + * derivation of the `payer_key`. This should not leak any + * information (such as using a simple BIP-32 derivation + * path); a valid system might be for a node to maintain a + * base payer key, and encode a 128-bit tweak here. The + * payer_key would be derived by tweaking the base key with + * SHA256(payer_base_pubkey || tweak). + */ + invreq->payer_info = tal_arr(invreq, u8, 16); + randombytes_buf(invreq->payer_info, + tal_bytelen(invreq->payer_info)); + } + + payer_key_tweak(&cmd->ld->bolt12_base, + invreq->payer_info, tal_bytelen(invreq->payer_info), + &tweakhash); + + /* Tweaking gives a not-x-only pubkey, must then convert. */ + if (secp256k1_xonly_pubkey_tweak_add(secp256k1_ctx, + &tweaked, + &cmd->ld->bolt12_base.pubkey, + tweakhash.u.u8) != 1) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid tweak"); + } + invreq->payer_key = tal(invreq, struct pubkey32); + if (secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, + &invreq->payer_key->pubkey, + NULL, &tweaked) != 1) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid tweaked key"); + } + + /* BOLT-offers #12: + * - if the offer contained `recurrence`: + *... + * - MUST set `recurrence_signature` `sig` as detailed in + * [Signature Calculation](#signature-calculation) using the + * `payer_key`. + */ + if (invreq->recurrence_counter) { + struct sha256 merkle; + u8 *msg; + + /* This populates the ->fields from our entries */ + invreq->fields = tlv_make_fields(invreq, invoice_request); + merkle_tlv(invreq->fields, &merkle); + + msg = towire_hsmd_sign_bolt12(NULL, + "invoice_request", + "recurrence_signature", + &merkle, invreq->payer_info); + if (!wire_sync_write(cmd->ld->hsm_fd, take(msg))) + fatal("Could not write to HSM: %s", strerror(errno)); + + msg = wire_sync_read(tmpctx, cmd->ld->hsm_fd); + invreq->recurrence_signature = tal(invreq, struct bip340sig); + if (!fromwire_hsmd_sign_bolt12_reply(msg, + invreq->recurrence_signature)) + fatal("HSM gave bad sign_offer_reply %s", + tal_hex(msg, msg)); + + /* FIXME: Validate signature! */ + } + + response = json_stream_success(cmd); + json_add_string(response, "bolt12", invrequest_encode(tmpctx, invreq)); + if (label) + json_add_escaped_string(response, "recurrence_label", + take(json_escape(NULL, label))); + return command_success(cmd, response); +} + +static const struct json_command createinvreq_command = { + "createinvoicerequest", + "payment", + json_createinvoicerequest, + "Create and sign an invoice_request {bolt12}, with {recurrence_label} if recurring, filling in payer_info and payer_key." +}; +AUTODATA(json_command, &createinvreq_command); diff --git a/wire/Makefile b/wire/Makefile index 77c6db01d..e6e1a4d5d 100644 --- a/wire/Makefile +++ b/wire/Makefile @@ -94,7 +94,7 @@ wire/peer_exp_wiregen.c_args := $(wire/peer_wiregen.c_args) wire/onion_exp_wiregen.h_args := $(wire/onion_wiregen.h_args) wire/onion_exp_wiregen.c_args := $(wire/onion_wiregen.c_args) -wire/bolt12_exp_wiregen.c_args := -s --expose-tlv-type=blinded_path +wire/bolt12_exp_wiregen.c_args := -s --expose-tlv-type=blinded_path --expose-tlv-type=invoice_request wire/bolt12_exp_wiregen.h_args := --include='bitcoin/short_channel_id.h' --include='bitcoin/signature.h' --include='bitcoin/privkey.h' --include='common/bigsize.h' --include='common/amount.h' --include='common/node_id.h' --include='bitcoin/block.h' --include='wire/onion_wire.h' $(wire/bolt12_exp_wiregen.c_args) wire/peer_wiregen.h_args := --include='common/channel_id.h' --include='bitcoin/tx.h' --include='bitcoin/preimage.h' --include='bitcoin/short_channel_id.h' --include='common/node_id.h' --include='common/bigsize.h' --include='bitcoin/block.h' --include='bitcoin/privkey.h' -s --expose-tlv-type=n1 --expose-tlv-type=n2 diff --git a/wire/common_wiregen.c b/wire/common_wiregen.c index 5066e6072..922ef39de 100644 --- a/wire/common_wiregen.c +++ b/wire/common_wiregen.c @@ -100,4 +100,4 @@ bool fromwire_custommsg_out(const tal_t *ctx, const void *p, u8 **msg) fromwire_u8_array(&cursor, &plen, *msg, msg_len); return cursor != NULL; } -// SHA256STAMP:18fdc0626e07a8b9b4d2eb82b765aafc45798a3337e38946967f947dfd94fc28 +// SHA256STAMP:3d4dd8c4e467b5a2158181d3b2e1e7f59f5082c8c5c91ba35d1fdda9a8d1ed6e diff --git a/wire/common_wiregen.h b/wire/common_wiregen.h index e8aefa833..1fe50cb38 100644 --- a/wire/common_wiregen.h +++ b/wire/common_wiregen.h @@ -41,4 +41,4 @@ bool fromwire_custommsg_out(const tal_t *ctx, const void *p, u8 **msg); #endif /* LIGHTNING_WIRE_COMMON_WIREGEN_H */ -// SHA256STAMP:18fdc0626e07a8b9b4d2eb82b765aafc45798a3337e38946967f947dfd94fc28 +// SHA256STAMP:3d4dd8c4e467b5a2158181d3b2e1e7f59f5082c8c5c91ba35d1fdda9a8d1ed6e diff --git a/wire/onion_printgen.c b/wire/onion_printgen.c index 65fd47fc0..b23bbcf26 100644 --- a/wire/onion_printgen.c +++ b/wire/onion_printgen.c @@ -653,4 +653,4 @@ void printonion_wire_tlv_message(const char *tlv_name, const u8 *msg) { printwire_tlvs(tlv_name, &msg, &plen, print_tlvs_tlv_payload, ARRAY_SIZE(print_tlvs_tlv_payload)); } } -// SHA256STAMP:76ca7e25b0fce4262e163356666de98f2b4b271a6bda0931ff22df17c6d93a35 +// SHA256STAMP:de983a18d6a298f6dc27229bc10eaaf846bc313e06450e23d122ea84de5c829a diff --git a/wire/onion_printgen.h b/wire/onion_printgen.h index 66e1f85bc..65e76d29f 100644 --- a/wire/onion_printgen.h +++ b/wire/onion_printgen.h @@ -57,4 +57,4 @@ void printwire_mpp_timeout(const char *fieldname, const u8 *cursor); #endif /* LIGHTNING_WIRE_ONION_PRINTGEN_H */ -// SHA256STAMP:76ca7e25b0fce4262e163356666de98f2b4b271a6bda0931ff22df17c6d93a35 +// SHA256STAMP:de983a18d6a298f6dc27229bc10eaaf846bc313e06450e23d122ea84de5c829a diff --git a/wire/onion_wiregen.c b/wire/onion_wiregen.c index 5e24091cc..8e53016c8 100644 --- a/wire/onion_wiregen.c +++ b/wire/onion_wiregen.c @@ -697,4 +697,4 @@ bool fromwire_mpp_timeout(const void *p) return false; return cursor != NULL; } -// SHA256STAMP:76ca7e25b0fce4262e163356666de98f2b4b271a6bda0931ff22df17c6d93a35 +// SHA256STAMP:de983a18d6a298f6dc27229bc10eaaf846bc313e06450e23d122ea84de5c829a diff --git a/wire/onion_wiregen.h b/wire/onion_wiregen.h index cc68210c6..caa3c01df 100644 --- a/wire/onion_wiregen.h +++ b/wire/onion_wiregen.h @@ -207,4 +207,4 @@ bool fromwire_mpp_timeout(const void *p); #endif /* LIGHTNING_WIRE_ONION_WIREGEN_H */ -// SHA256STAMP:76ca7e25b0fce4262e163356666de98f2b4b271a6bda0931ff22df17c6d93a35 +// SHA256STAMP:de983a18d6a298f6dc27229bc10eaaf846bc313e06450e23d122ea84de5c829a diff --git a/wire/peer_printgen.c b/wire/peer_printgen.c index a6f7a247f..64a2d4612 100644 --- a/wire/peer_printgen.c +++ b/wire/peer_printgen.c @@ -2036,4 +2036,4 @@ void printpeer_wire_tlv_message(const char *tlv_name, const u8 *msg) { printwire_tlvs(tlv_name, &msg, &plen, print_tlvs_reply_channel_range_tlvs, ARRAY_SIZE(print_tlvs_reply_channel_range_tlvs)); } } -// SHA256STAMP:91aa8c2e798052d38eefeab6a13563b213358e508b772bf7b221186d7c9d08ef +// SHA256STAMP:7cb56bd1ecb24076a620bf103cd28fc31cd45a31e2d6e59a1fd1f2a742d520fd diff --git a/wire/peer_printgen.h b/wire/peer_printgen.h index 592be8bad..b4361dd9a 100644 --- a/wire/peer_printgen.h +++ b/wire/peer_printgen.h @@ -70,4 +70,4 @@ void printwire_gossip_timestamp_filter(const char *fieldname, const u8 *cursor); void printwire_channel_update_checksums(const char *fieldname, const u8 **cursor, size_t *plen); void printwire_channel_update_timestamps(const char *fieldname, const u8 **cursor, size_t *plen); #endif /* LIGHTNING_WIRE_PEER_PRINTGEN_H */ -// SHA256STAMP:91aa8c2e798052d38eefeab6a13563b213358e508b772bf7b221186d7c9d08ef +// SHA256STAMP:7cb56bd1ecb24076a620bf103cd28fc31cd45a31e2d6e59a1fd1f2a742d520fd diff --git a/wire/peer_wiregen.c b/wire/peer_wiregen.c index 5df582362..8dd90452f 100644 --- a/wire/peer_wiregen.c +++ b/wire/peer_wiregen.c @@ -1630,4 +1630,4 @@ bool fromwire_channel_update_option_channel_htlc_max(const void *p, secp256k1_ec *htlc_maximum_msat = fromwire_amount_msat(&cursor, &plen); return cursor != NULL; } -// SHA256STAMP:91aa8c2e798052d38eefeab6a13563b213358e508b772bf7b221186d7c9d08ef +// SHA256STAMP:7cb56bd1ecb24076a620bf103cd28fc31cd45a31e2d6e59a1fd1f2a742d520fd diff --git a/wire/peer_wiregen.h b/wire/peer_wiregen.h index 61201e461..64bdd9ab3 100644 --- a/wire/peer_wiregen.h +++ b/wire/peer_wiregen.h @@ -595,4 +595,4 @@ bool fromwire_channel_update_option_channel_htlc_max(const void *p, secp256k1_ec #endif /* LIGHTNING_WIRE_PEER_WIREGEN_H */ -// SHA256STAMP:91aa8c2e798052d38eefeab6a13563b213358e508b772bf7b221186d7c9d08ef +// SHA256STAMP:7cb56bd1ecb24076a620bf103cd28fc31cd45a31e2d6e59a1fd1f2a742d520fd