lightningd: Add signinvoice to sign a BOLT11 invoice.

Though there's already a `createinvoice` command, there are usecases where a
user may want to sign an invoice that they don't yet have the preimage to. For
example, they may have an htlc_accepted plugin that pays to obtain the preimage
from someone else and returns a `{ "result": "resolve", ... }`.

This RPC command addresses this usecase without overly complicating the
semantics of the existing `createinvoice` command.

Changelog-Added: JSON-RPC: `signinvoice` new command to sign BOLT11
invoices.
This commit is contained in:
Carl Dong 2022-11-06 19:45:47 -05:00 committed by Alex Myers
parent f0b8544eba
commit 1dbc29b8c0
7 changed files with 149 additions and 0 deletions

View File

@ -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 \

View File

@ -116,6 +116,7 @@ Core Lightning Documentation
lightning-sendpay <lightning-sendpay.7.md>
lightning-sendpsbt <lightning-sendpsbt.7.md>
lightning-setchannel <lightning-setchannel.7.md>
lightning-signinvoice <lightning-signinvoice.7.md>
lightning-signmessage <lightning-signmessage.7.md>
lightning-signpsbt <lightning-signpsbt.7.md>
lightning-sql <lightning-sql.7.md>

View File

@ -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 <<contact@carldong.me>> 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: <https://github.com/ElementsProject/lightning>
[comment]: # ( SHA256STAMP:9348784bd3daaed1cd35b29b2e5c91ea17bc8e11bf5bb6e1de9a098241cb74d6)

View File

@ -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": ""
}
}
}

View File

@ -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"
}
}
}

View File

@ -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);

View File

@ -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.