mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-11 01:27:58 +01:00
hsmd: add hsmd_preapprove_invoice and check_preapproveinvoice pay modifier
Changelog-added: hsmd: A new message `hsmd_preapprove_invoice` is added. Changelog-added: JSON-RPC: A new command `preapproveinvoice` is added.
This commit is contained in:
parent
9482e0619c
commit
f29343d740
14 changed files with 205 additions and 0 deletions
|
@ -46,6 +46,7 @@ enum jsonrpc_errcode {
|
|||
PAY_STOPPED_RETRYING = 210,
|
||||
PAY_STATUS_UNEXPECTED = 211,
|
||||
PAY_INVOICE_REQUEST_INVALID = 212,
|
||||
PAY_INVOICE_PREAPPROVAL_DECLINED = 213,
|
||||
|
||||
/* `fundchannel` or `withdraw` errors */
|
||||
FUND_MAX_EXCEEDED = 300,
|
||||
|
|
|
@ -75,6 +75,7 @@ MANPAGES := doc/lightning-cli.1 \
|
|||
doc/lightning-pay.7 \
|
||||
doc/lightning-parsefeerate.7 \
|
||||
doc/lightning-plugin.7 \
|
||||
doc/lightning-preapproveinvoice.7 \
|
||||
doc/lightning-recoverchannel.7 \
|
||||
doc/lightning-reserveinputs.7 \
|
||||
doc/lightning-sendinvoice.7 \
|
||||
|
|
|
@ -105,6 +105,7 @@ Core Lightning Documentation
|
|||
lightning-pay <lightning-pay.7.md>
|
||||
lightning-ping <lightning-ping.7.md>
|
||||
lightning-plugin <lightning-plugin.7.md>
|
||||
lightning-preapproveinvoice <lightning-preapproveinvoice.7.md>
|
||||
lightning-recoverchannel <lightning-recoverchannel.7.md>
|
||||
lightning-reserveinputs <lightning-reserveinputs.7.md>
|
||||
lightning-sendcustommsg <lightning-sendcustommsg.7.md>
|
||||
|
|
51
doc/lightning-preapproveinvoice.7.md
Normal file
51
doc/lightning-preapproveinvoice.7.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
lightning-preapproveinvoice -- Ask the HSM to preapprove an invoice (low-level)
|
||||
==================================================================
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
|
||||
**preapproveinvoice** *bolt11*
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
The **preapproveinvoice** RPC command submits the *bolt11* invoice to
|
||||
the HSM to check that it is approved for payment.
|
||||
|
||||
Generally the **preapproveinvoice** request does not need to be made
|
||||
explicitly, it is automatically generated as part of a **pay** request.
|
||||
|
||||
By default, the HSM will approve all **preapproveinvoice** requests.
|
||||
|
||||
If a remote signer is being used it might decline an **preapproveinvoice**
|
||||
request because it would exceed velocity controls, is not covered by
|
||||
allowlist controls, was declined manually, or other reasons.
|
||||
|
||||
If a remote signer declines a **preapproveinvoice** request a subsequent
|
||||
attempt to pay the invoice anyway will fail; the signer will refuse to sign
|
||||
the commitment.
|
||||
|
||||
RETURN VALUE
|
||||
------------
|
||||
|
||||
[comment]: # (GENERATE-FROM-SCHEMA-START)
|
||||
On success, an empty object is returned.
|
||||
|
||||
[comment]: # (GENERATE-FROM-SCHEMA-END)
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
Ken Sedgwick <<ken@bonsai.com>> is mainly responsible.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
lightning-pay(7)
|
||||
|
||||
RESOURCES
|
||||
---------
|
||||
|
||||
Main web site: <https://github.com/ElementsProject/lightning>
|
||||
|
||||
[comment]: # ( SHA256STAMP:735dd61146b04745f1e884037ead662a386fec2c41e2de1a8698d6bb03f63540)
|
13
doc/schemas/preapproveinvoice.request.json
Normal file
13
doc/schemas/preapproveinvoice.request.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"bolt11"
|
||||
],
|
||||
"properties": {
|
||||
"bolt11": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
6
doc/schemas/preapproveinvoice.schema.json
Normal file
6
doc/schemas/preapproveinvoice.schema.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
|
@ -668,6 +668,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c)
|
|||
case WIRE_HSMD_SIGN_MESSAGE:
|
||||
case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER:
|
||||
case WIRE_HSMD_SIGN_BOLT12:
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE:
|
||||
case WIRE_HSMD_ECDH_REQ:
|
||||
case WIRE_HSMD_CHECK_FUTURE_SECRET:
|
||||
case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY:
|
||||
|
@ -708,6 +709,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c)
|
|||
case WIRE_HSMD_SIGN_MESSAGE_REPLY:
|
||||
case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY:
|
||||
case WIRE_HSMD_SIGN_BOLT12_REPLY:
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY:
|
||||
return bad_req_fmt(conn, c, c->msg_in,
|
||||
"Received an incoming message of type %s, "
|
||||
"which is not a request",
|
||||
|
|
|
@ -113,6 +113,14 @@ msgdata,hsmd_sign_invoice,hrp,u8,hrplen
|
|||
msgtype,hsmd_sign_invoice_reply,108
|
||||
msgdata,hsmd_sign_invoice_reply,sig,secp256k1_ecdsa_recoverable_signature,
|
||||
|
||||
# Preapprove an invoice for payment
|
||||
msgtype,hsmd_preapprove_invoice,38
|
||||
msgdata,hsmd_preapprove_invoice,invstring,wirestring,
|
||||
|
||||
# Result is true if approved, declined if false
|
||||
msgtype,hsmd_preapprove_invoice_reply,138
|
||||
msgdata,hsmd_preapprove_invoice_reply,approved,bool,
|
||||
|
||||
# Give me ECDH(node-id-secret,point)
|
||||
msgtype,hsmd_ecdh_req,1
|
||||
msgdata,hsmd_ecdh_req,point,pubkey,
|
||||
|
|
|
|
@ -119,6 +119,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client,
|
|||
case WIRE_HSMD_SIGN_MESSAGE:
|
||||
case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY:
|
||||
case WIRE_HSMD_SIGN_BOLT12:
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE:
|
||||
case WIRE_HSMD_DERIVE_SECRET:
|
||||
return (client->capabilities & HSM_CAP_MASTER) != 0;
|
||||
|
||||
|
@ -149,6 +150,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client,
|
|||
case WIRE_HSMD_SIGN_MESSAGE_REPLY:
|
||||
case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY:
|
||||
case WIRE_HSMD_SIGN_BOLT12_REPLY:
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY:
|
||||
case WIRE_HSMD_DERIVE_SECRET_REPLY:
|
||||
break;
|
||||
}
|
||||
|
@ -659,6 +661,22 @@ static u8 *handle_sign_bolt12(struct hsmd_client *c, const u8 *msg_in)
|
|||
return towire_hsmd_sign_bolt12_reply(NULL, &sig);
|
||||
}
|
||||
|
||||
/*~ lightningd asks us to approve an invoice. This stub implementation
|
||||
* is overriden by fully validating signers that need to track invoice
|
||||
* payments. */
|
||||
static u8 *handle_preapprove_invoice(struct hsmd_client *c, const u8 *msg_in)
|
||||
{
|
||||
char *invstring;
|
||||
bool approved;
|
||||
if (!fromwire_hsmd_preapprove_invoice(tmpctx, msg_in, &invstring))
|
||||
return hsmd_status_malformed_request(c, msg_in);
|
||||
|
||||
/* This stub always approves */
|
||||
approved = true;
|
||||
|
||||
return towire_hsmd_preapprove_invoice_reply(NULL, approved);
|
||||
}
|
||||
|
||||
/*~ Lightning invoices, defined by BOLT 11, are signed. This has been
|
||||
* surprisingly controversial; it means a node needs to be online to create
|
||||
* invoices. However, it seems clear to me that in a world without
|
||||
|
@ -1572,6 +1590,8 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
|||
return handle_sign_option_will_fund_offer(client, msg);
|
||||
case WIRE_HSMD_SIGN_BOLT12:
|
||||
return handle_sign_bolt12(client, msg);
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE:
|
||||
return handle_preapprove_invoice(client, msg);
|
||||
case WIRE_HSMD_SIGN_MESSAGE:
|
||||
return handle_sign_message(client, msg);
|
||||
case WIRE_HSMD_GET_CHANNEL_BASEPOINTS:
|
||||
|
@ -1635,6 +1655,7 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
|||
case WIRE_HSMD_SIGN_MESSAGE_REPLY:
|
||||
case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY:
|
||||
case WIRE_HSMD_SIGN_BOLT12_REPLY:
|
||||
case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY:
|
||||
break;
|
||||
}
|
||||
return hsmd_status_bad_request(client, msg, "Unknown request");
|
||||
|
|
|
@ -1808,3 +1808,48 @@ static const struct json_command createinvoice_command = {
|
|||
};
|
||||
|
||||
AUTODATA(json_command, &createinvoice_command);
|
||||
|
||||
static struct command_result *json_preapproveinvoice(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
const char *invstring;
|
||||
struct json_stream *response;
|
||||
bool approved;
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
/* FIXME: parameter should be invstring now */
|
||||
p_req("bolt11", param_string, &invstring),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
/* Strip optional URI preamble. */
|
||||
if (strncmp(invstring, "lightning:", 10) == 0 ||
|
||||
strncmp(invstring, "LIGHTNING:", 10) == 0)
|
||||
invstring += 10;
|
||||
|
||||
u8 *msg = towire_hsmd_preapprove_invoice(NULL, invstring);
|
||||
|
||||
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);
|
||||
if (!fromwire_hsmd_preapprove_invoice_reply(msg, &approved))
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"HSM gave bad preapprove_invoice_reply %s", tal_hex(msg, msg));
|
||||
|
||||
if (!approved)
|
||||
return command_fail(cmd, PAY_INVOICE_PREAPPROVAL_DECLINED, "invoice was declined");
|
||||
|
||||
response = json_stream_success(cmd);
|
||||
return command_success(cmd, response);
|
||||
}
|
||||
|
||||
static const struct json_command preapproveinvoice_command = {
|
||||
"preapproveinvoice",
|
||||
"payment",
|
||||
json_preapproveinvoice,
|
||||
"Ask the HSM to preapprove an invoice."
|
||||
};
|
||||
AUTODATA(json_command, &preapproveinvoice_command);
|
||||
|
|
|
@ -255,6 +255,9 @@ bool fromwire_dualopend_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNN
|
|||
/* Generated stub for fromwire_hsmd_sign_bolt12_reply */
|
||||
bool fromwire_hsmd_sign_bolt12_reply(const void *p UNNEEDED, struct bip340sig *sig UNNEEDED)
|
||||
{ fprintf(stderr, "fromwire_hsmd_sign_bolt12_reply called!\n"); abort(); }
|
||||
/* Generated stub for fromwire_hsmd_preapprove_invoice_reply */
|
||||
bool fromwire_hsmd_preapprove_invoice_reply(const void *p UNNEEDED, bool *approved UNNEEDED)
|
||||
{ fprintf(stderr, "fromwire_hsmd_preapprove_invoice_reply called!\n"); abort(); }
|
||||
/* Generated stub for fromwire_hsmd_sign_commitment_tx_reply */
|
||||
bool fromwire_hsmd_sign_commitment_tx_reply(const void *p UNNEEDED, struct bitcoin_signature *sig UNNEEDED)
|
||||
{ fprintf(stderr, "fromwire_hsmd_sign_commitment_tx_reply called!\n"); abort(); }
|
||||
|
@ -777,6 +780,9 @@ u8 *towire_gossipd_discovered_ip(const tal_t *ctx UNNEEDED, const struct wireadd
|
|||
/* Generated stub for towire_hsmd_sign_bolt12 */
|
||||
u8 *towire_hsmd_sign_bolt12(const tal_t *ctx UNNEEDED, const wirestring *messagename UNNEEDED, const wirestring *fieldname UNNEEDED, const struct sha256 *merkleroot UNNEEDED, const u8 *publictweak UNNEEDED)
|
||||
{ fprintf(stderr, "towire_hsmd_sign_bolt12 called!\n"); abort(); }
|
||||
/* Generated stub for towire_hsmd_preapprove_invoice */
|
||||
u8 *towire_hsmd_preapprove_invoice(const tal_t *ctx UNNEEDED, const wirestring *invstring UNNEEDED)
|
||||
{ fprintf(stderr, "towire_hsmd_preapprove_invoice called!\n"); abort(); }
|
||||
/* Generated stub for towire_hsmd_sign_commitment_tx */
|
||||
u8 *towire_hsmd_sign_commitment_tx(const tal_t *ctx UNNEEDED, const struct node_id *peer_id UNNEEDED, u64 channel_dbid UNNEEDED, const struct bitcoin_tx *tx UNNEEDED, const struct pubkey *remote_funding_key UNNEEDED, u64 commit_num UNNEEDED)
|
||||
{ fprintf(stderr, "towire_hsmd_sign_commitment_tx called!\n"); abort(); }
|
||||
|
|
|
@ -3905,6 +3905,54 @@ static void payee_incoming_limit_step_cb(void *d UNUSED, struct payment *p)
|
|||
REGISTER_PAYMENT_MODIFIER(payee_incoming_limit, void *, NULL,
|
||||
payee_incoming_limit_step_cb);
|
||||
|
||||
/*****************************************************************************
|
||||
* check_preapproveinvoice
|
||||
*
|
||||
* @desc submit the invoice to the HSM for approval, fail the payment if not approved.
|
||||
*
|
||||
* This paymod checks the invoice for approval with the HSM, which might:
|
||||
* - check with the user for specific approval
|
||||
* - enforce velocity controls
|
||||
* - automatically approve the invoice (default)
|
||||
*/
|
||||
|
||||
static struct command_result *
|
||||
check_preapproveinvoice_allow(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct payment *p)
|
||||
{
|
||||
/* On success, an empty object is returned. */
|
||||
payment_continue(p);
|
||||
return command_still_pending(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *preapproveinvoice_rpc_failure(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *toks,
|
||||
struct payment *p)
|
||||
{
|
||||
payment_abort(p,
|
||||
"Failing payment due to a failed RPC call: %.*s",
|
||||
toks->end - toks->start, buffer + toks->start);
|
||||
return command_still_pending(cmd);
|
||||
}
|
||||
|
||||
static void check_preapproveinvoice_start(void *d UNUSED, struct payment *p)
|
||||
{
|
||||
/* Ask the HSM if the invoice is OK to pay */
|
||||
struct out_req *req;
|
||||
req = jsonrpc_request_start(p->plugin, NULL, "preapproveinvoice",
|
||||
&check_preapproveinvoice_allow,
|
||||
&preapproveinvoice_rpc_failure, p);
|
||||
/* FIXME: rename parameter to invstring */
|
||||
json_add_string(req->js, "bolt11", p->invstring);
|
||||
(void) send_outreq(p->plugin, req);
|
||||
}
|
||||
|
||||
REGISTER_PAYMENT_MODIFIER(check_preapproveinvoice, void *, NULL,
|
||||
check_preapproveinvoice_start);
|
||||
|
||||
static struct route_exclusions_data *
|
||||
route_exclusions_data_init(struct payment *p)
|
||||
{
|
||||
|
|
|
@ -448,6 +448,7 @@ REGISTER_PAYMENT_MODIFIER_HEADER(local_channel_hints, void);
|
|||
* each of those channels can bear. */
|
||||
REGISTER_PAYMENT_MODIFIER_HEADER(payee_incoming_limit, void);
|
||||
REGISTER_PAYMENT_MODIFIER_HEADER(route_exclusions, struct route_exclusions_data);
|
||||
REGISTER_PAYMENT_MODIFIER_HEADER(check_preapproveinvoice, void);
|
||||
|
||||
|
||||
struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
||||
|
|
|
@ -928,6 +928,7 @@ payment_listsendpays_previous(struct command *cmd, const char *buf,
|
|||
}
|
||||
|
||||
struct payment_modifier *paymod_mods[] = {
|
||||
&check_preapproveinvoice_pay_mod,
|
||||
/* NOTE: The order in which these four paymods are executed is
|
||||
* significant!
|
||||
* local_channel_hints *must* execute first before route_exclusions
|
||||
|
|
Loading…
Add table
Reference in a new issue