diff --git a/doc/Makefile b/doc/Makefile index 560e6782d..a2e158e3d 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -84,6 +84,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-sendpay.7 \ doc/lightning-setchannel.7 \ doc/lightning-sendcustommsg.7 \ + doc/lightning-signinvoice.7 \ doc/lightning-signmessage.7 \ doc/lightning-staticbackup.7 \ doc/lightning-txprepare.7 \ diff --git a/doc/index.rst b/doc/index.rst index b0d8ae799..1be476a41 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -116,6 +116,7 @@ Core Lightning Documentation lightning-sendpay lightning-sendpsbt lightning-setchannel + lightning-signinvoice lightning-signmessage lightning-signpsbt lightning-sql diff --git a/doc/lightning-signinvoice.7.md b/doc/lightning-signinvoice.7.md new file mode 100644 index 000000000..8faebbef9 --- /dev/null +++ b/doc/lightning-signinvoice.7.md @@ -0,0 +1,51 @@ +lightning-signinvoice -- Low-level invoice signing +===================================================== + +SYNOPSIS +-------- + +**signinvoice** *invstring* + +DESCRIPTION +----------- + +The **signinvoice** RPC command signs an invoice. Unlike +**createinvoice** it does not save the invoice into the database and +thus does not require the preimage. + +The *invstring* parameter is of bolt11 form, but the final signature +is ignored. Minimal sanity checks are done. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object is returned, containing: + +- **bolt11** (string): the bolt11 string + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +On failure, an error is returned. + +The following error codes may occur: +- -1: Catchall nonspecific error. + +AUTHOR +------ + +Carl Dong <> is mainly responsible. + +SEE ALSO +-------- + +lightning-createinvoice(7), lightning-invoice(7), lightning-listinvoices(7), +lightning-delinvoice(7), lightning-getroute(7), lightning-sendpay(7), +lightning-offer(7). + +RESOURCES +--------- + +Main web site: + +[comment]: # ( SHA256STAMP:9348784bd3daaed1cd35b29b2e5c91ea17bc8e11bf5bb6e1de9a098241cb74d6) diff --git a/doc/schemas/signinvoice.request.json b/doc/schemas/signinvoice.request.json new file mode 100644 index 000000000..40b8e3f46 --- /dev/null +++ b/doc/schemas/signinvoice.request.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "added": "v23.02", + "required": [ + "invstring" + ], + "properties": { + "invstring": { + "type": "string", + "description": "" + } + } +} diff --git a/doc/schemas/signinvoice.schema.json b/doc/schemas/signinvoice.schema.json new file mode 100644 index 000000000..bf9be4741 --- /dev/null +++ b/doc/schemas/signinvoice.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "bolt11" + ], + "properties": { + "bolt11": { + "type": "string", + "description": "the bolt11 string" + } + } +} diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 1c890931b..d83eb658e 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -1897,3 +1897,57 @@ static const struct json_command preapprovekeysend_command = { "Ask the HSM to preapprove a keysend payment." }; AUTODATA(json_command, &preapprovekeysend_command); + +static struct command_result *json_signinvoice(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + const char *invstring; + struct json_stream *response; + struct bolt11 *b11; + struct sha256 hash; + const u5 *sig; + bool have_n; + char *fail; + + if (!param(cmd, buffer, params, + p_req("invstring", param_string, &invstring), + NULL)) + return command_param_failed(); + + b11 = bolt11_decode_nosig(cmd, invstring, cmd->ld->our_features, + NULL, chainparams, &hash, &sig, &have_n, + &fail); + + if (!b11) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Unparsable invoice '%s': %s", + invstring, fail); + + /* This adds the signature */ + char *b11enc = bolt11_encode(cmd, b11, have_n, + hsm_sign_b11, cmd->ld); + + /* BOLT #11: + * A writer: + *... + * - MUST include either exactly one `d` or exactly one `h` field. + */ + if (!b11->description && !b11->description_hash) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Missing description in invoice"); + + response = json_stream_success(cmd); + json_add_invstring(response, b11enc); + return command_success(cmd, response); +} + +static const struct json_command signinvoice_command = { + "signinvoice", + "payment", + json_signinvoice, + "Lowlevel command to sign invoice {invstring}." +}; + +AUTODATA(json_command, &signinvoice_command); diff --git a/tests/test_invoices.py b/tests/test_invoices.py index 7428827cf..83aa56d2d 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -533,6 +533,19 @@ def test_waitanyinvoice(node_factory, executor): l2.rpc.waitanyinvoice('non-number') +def test_signinvoice(node_factory, executor): + # Setup + l1, l2 = node_factory.line_graph(2) + + # Create an invoice for l1 + inv1 = l1.rpc.invoice(1000, 'inv1', 'inv1')['bolt11'] + assert l1.rpc.decodepay(inv1)['payee'] == l1.info['id'] + + # Have l2 re-sign the invoice + inv2 = l2.rpc.signinvoice(inv1)['bolt11'] + assert l1.rpc.decodepay(inv2)['payee'] == l2.info['id'] + + def test_waitanyinvoice_reversed(node_factory, executor): """Test waiting for invoices, where they are paid in reverse order to when they are created.