Core Lightning implementation of BOLT #11 invoices - part 8
LIVE #24・March 14, 2024
In this last session about BOLT #11 invoices, we implement a very simplified version of the Core Lightning invoice
command.
Terminal session
We ran the following commands in this order:
$ source contrib/startup_regtest.sh
$ make clean
$ ./configure --disable-rust
$ make -j12
$ start_ln
$ l1-cli getinfo | jq .id
$ alias l1-cli
$ ps -ax | rg lightningd
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
$ ./lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli my-invoice 10000 my-description
$ l1-cli decode lnbcrt100n1pjlxf8spp532n4s9yug5xs5ahclh2u2lxva0c4kz4g26y0fusdc6am5wesm3eqsp5msq5pcv3q0vw75wtfglrf7z6hmskuumhpw5hsw0hpyqk37hzyzqqdqhd4uj6er9wd3hy6tsw35k7ms78ny4kuh94t39nju3r7erulp5f2zwa6efermmx26rcml6xrnqzlj9ykd7p0u2fxljv2hkf7ktu2sm2ujyt53e58593hvh0n7zw523ugqrtelvr
And below you can read the terminal session (command lines and outputs):
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/work/repos/lightning/cli/lightning-cli
lightningd is /home/tony/work/repos/lightning/lightningd/lightningd
Useful commands:
start_ln 3: start three nodes, l1, l2, l3
connect 1 2: connect l1 and l2
fund_nodes: connect all nodes with channels, in a row
stop_ln: shutdown
destroy_ln: remove ln directories
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ make clean
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ ./configure --disable-rust
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ make -j12
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 3085776
[2] 3085816
WARNING: eatmydata not found: install it for faster testing
Commands:
l1-cli, l1-log,
l2-cli, l2-log,
bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli getinfo | jq .id
"02e615099b052918a41d1b988703660bcb5174b512dde64c89601b21700095f601"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ alias l1-cli
alias l1-cli='/home/tony/work/repos/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ ps -ax | rg lightningd
3085778 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
3085818 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l2-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
3085830 pts/0 SL 0:00 /home/tony/work/repos/lightning/lightningd/lightning_hsmd --developer
3085854 pts/0 SL 0:00 /home/tony/work/repos/lightning/lightningd/lightning_hsmd --developer
3085855 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightning_connectd --developer
3085865 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightning_connectd --developer
3085866 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightning_gossipd --developer
3085879 pts/0 S 0:00 /home/tony/work/repos/lightning/lightningd/lightning_gossipd --developer
3099672 pts/0 S+ 0:00 rg lightningd
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ ./lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"amount_msat": 10000,
"description": "my-description"
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt....",
"amount_msat": 10000,
"description": "my-description"
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt....",
"msat": 10000,
"description": "my-description"
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt....",
"msat": 10000,
"description": "my-description",
"timestamp": 1710431442
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt....",
"msat": 10000,
"description": "my-description",
"network_name": "regtest",
"lightning_hrp": "bcrt",
"timestamp": 1710431603
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt....",
"msat": 10000,
"description": "my-description",
"payment_hash": "e1493097b6538ea1eb482ac829162e687e0436370273fc3522713840a859b88e",
"payment_secret": "02216c83892302853f3b89b5d6233cb2c8234a336e5ceaf4b87ce232de1fe08c",
"network_name": "regtest",
"lightning_hrp": "bcrt",
"timestamp": 1710432241
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "my-description",
"msat": 10000,
"description": "my-description",
"payment_hash": "26730a1acc9c48924f4862f9eb651194840545811ed2eea3eecb39076378a503",
"payment_secret": "eddd3378c1bddc459036c27a7e9a96f1bbe3078a1f06eeb747debba180bd4ca2",
"network_name": "regtest",
"lightning_hrp": "bcrt",
"timestamp": 1710432724
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli my-invoice 10000 my-description
{
"bolt11": "lnbcrt100n1pjlxf8spp532n4s9yug5xs5ahclh2u2lxva0c4kz4g26y0fusdc6am5wesm3eqsp5msq5pcv3q0vw75wtfglrf7z6hmskuumhpw5hsw0hpyqk37hzyzqqdqhd4uj6er9wd3hy6tsw35k7ms78ny4kuh94t39nju3r7erulp5f2zwa6efermmx26rcml6xrnqzlj9ykd7p0u2fxljv2hkf7ktu2sm2ujyt53e58593hvh0n7zw523ugqrtelvr",
"msat": 10000,
"description": "my-description",
"payment_hash": "8aa758149c450d0a76f8fdd5c57cccebf15b0aa85688f4f20dc6bbba3b30dc72",
"payment_secret": "dc0140e19103d8ef51cb4a3e34f85abee16e73770ba97839f7090168fae22080",
"network_name": "regtest",
"lightning_hrp": "bcrt",
"timestamp": 1710433520
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli decode lnbcrt100n1pjlxf8spp532n4s9yug5xs5ahclh2u2lxva0c4kz4g26y0fusdc6am5wesm3eqsp5msq5pcv3q0vw75wtfglrf7z6hmskuumhpw5hsw0hpyqk37hzyzqqdqhd4uj6er9wd3hy6tsw35k7ms78ny4kuh94t39nju3r7erulp5f2zwa6efermmx26rcml6xrnqzlj9ykd7p0u2fxljv2hkf7ktu2sm2ujyt53e58593hvh0n7zw523ugqrtelvr
{
"type": "bolt11 invoice",
"currency": "bcrt",
"created_at": 1710433520,
"expiry": 3600,
"payee": "02e615099b052918a41d1b988703660bcb5174b512dde64c89601b21700095f601",
"amount_msat": 10000,
"description": "my-description",
"min_final_cltv_expiry": 18,
"payment_secret": "dc0140e19103d8ef51cb4a3e34f85abee16e73770ba97839f7090168fae22080",
"features": "",
"payment_hash": "8aa758149c450d0a76f8fdd5c57cccebf15b0aa85688f4f20dc6bbba3b30dc72",
"signature": "3045022100f1e64adb972d5712ce5c88fd91f3e1a2542777594e47bd995a1e37fd187300bf02202292cdf05fc524df93157b27d65f150dab9222e91cd0f42c6ecbbe7e13a8a8f1",
"valid": true
}
Core Lightning Source Code
json_invoice
json_invoice in lightningd/invoice.c
static struct command_result *json_invoice(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
const jsmntok_t *fallbacks;
struct amount_msat *msatoshi_val;
struct invoice_info *info;
const char *desc_val;
const u8 **fallback_scripts = NULL;
u64 *expiry;
struct sha256 rhash;
struct secret payment_secret;
struct preimage *preimage;
u32 *cltv;
struct jsonrpc_request *req;
struct plugin *plugin;
bool *hashonly;
const size_t inv_max_label_len = 128;
const jsmntok_t *dev_routes;
info = tal(cmd, struct invoice_info);
info->cmd = cmd;
if (!param_check(cmd, buffer, params,
p_req("amount_msat|msatoshi", param_positive_msat_or_any, &msatoshi_val),
p_req("label", param_label, &info->label),
p_req("description", param_escaped_string, &desc_val),
p_opt_def("expiry", param_u64, &expiry, 3600*24*7),
p_opt("fallbacks", param_array, &fallbacks),
p_opt("preimage", param_preimage, &preimage),
p_opt("exposeprivatechannels", param_chanhints,
&info->chanhints),
p_opt_def("cltv", param_number, &cltv,
cmd->ld->config.cltv_final),
p_opt_def("deschashonly", param_bool, &hashonly, false),
p_opt("dev-routes", param_array, &dev_routes),
NULL))
return command_param_failed();
if (dev_routes && !cmd->ld->developer)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"dev-routes requires --developer");
if (strlen(info->label->s) > inv_max_label_len) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Label '%s' over %zu bytes", info->label->s, inv_max_label_len);
}
if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Descriptions greater than %d bytes "
"not yet supported "
"(description length %zu)",
BOLT11_FIELD_BYTE_LIMIT,
strlen(desc_val));
}
if (command_check_only(cmd))
return command_check_done(cmd);
info->custom_fallbacks = false;
if (fallbacks) {
info->custom_fallbacks = true;
if (cmd->ld->unified_invoices) {
log_info(cmd->ld->log,
"WARNING: Not tracking on-chain payments "
"for custom fallback addresses");
}
size_t i;
const jsmntok_t *t;
fallback_scripts = tal_arr(cmd, const u8 *, fallbacks->size);
json_for_each_arr(i, t, fallbacks) {
struct command_result *r;
r = parse_fallback(cmd, buffer, t, &fallback_scripts[i]);
if (r)
return r;
}
} else if (cmd->ld->unified_invoices) {
struct pubkey pubkey;
const u8 *p2tr;
fallback_scripts = tal_arr(cmd, const u8 *, 1);
if (!newaddr_inner(cmd, &pubkey, ADDR_P2TR))
return command_fail(cmd, LIGHTNINGD, "Keys exhausted ");
p2tr = scriptpubkey_p2tr(fallback_scripts, &pubkey);
fallback_scripts[0] = p2tr;
}
if (preimage)
info->payment_preimage = *preimage;
else
/* Generate random secret preimage. */
randombytes_buf(&info->payment_preimage,
sizeof(info->payment_preimage));
/* Generate preimage hash. */
sha256(&rhash, &info->payment_preimage, sizeof(info->payment_preimage));
/* Generate payment secret. */
invoice_secret(&info->payment_preimage, &payment_secret);
info->b11 = new_bolt11(info, msatoshi_val);
info->b11->chain = chainparams;
info->b11->timestamp = time_now().ts.tv_sec;
info->b11->payment_hash = rhash;
info->b11->receiver_id = cmd->ld->id;
info->b11->min_final_cltv_expiry = *cltv;
info->b11->expiry = *expiry;
info->b11->description = tal_steal(info->b11, desc_val);
/* BOLT #11:
* * `h` (23): `data_length` 52. 256-bit description of purpose of payment (SHA256).
*...
* A writer:
*...
* - MUST include either exactly one `d` or exactly one `h` field.
*/
if (*hashonly) {
info->b11->description_hash = tal(info->b11, struct sha256);
sha256(info->b11->description_hash, desc_val, strlen(desc_val));
} else
info->b11->description_hash = NULL;
info->b11->payment_secret = tal_dup(info->b11, struct secret,
&payment_secret);
info->b11->features = tal_dup_talarr(info->b11, u8,
cmd->ld->our_features
->bits[BOLT11_FEATURE]);
info->b11->routes = unpack_routes(info->b11, buffer, dev_routes);
if (fallback_scripts)
info->b11->fallbacks = tal_steal(info->b11, fallback_scripts);
/* We can't generate routehints without listincoming. */
plugin = find_plugin_for_command(cmd->ld, "listincoming");
if (!plugin) {
return invoice_complete(info, true,
false, false, false, false, false);
}
req = jsonrpc_request_start(info, "listincoming",
cmd->id, plugin->non_numeric_ids,
command_log(cmd),
NULL, listincoming_done,
info);
jsonrpc_request_end(req);
plugin_request_send(plugin, req);
return command_still_pending(cmd);
}
static const struct json_command invoice_command = {
"invoice",
"payment",
json_invoice,
"Create an invoice for {msatoshi} with {label} "
"and {description} with optional {expiry} seconds "
"(default 1 week), optional {fallbacks} address list"
"(default empty list) and optional {preimage} "
"(default autogenerated)"};
AUTODATA(json_command, &invoice_command);
invoice_complete
invoice_complete in lightningd/invoice.c
static struct command_result *
invoice_complete(struct invoice_info *info,
bool warning_no_listincoming,
bool warning_mpp,
bool warning_capacity,
bool warning_deadends,
bool warning_offline,
bool warning_private_unused)
{
struct json_stream *response;
u64 inv_dbid;
char *b11enc;
const struct invoice_details *details;
struct secret payment_secret;
struct wallet *wallet = info->cmd->ld->wallet;
b11enc = bolt11_encode(info, info->b11, false,
hsm_sign_b11, info->cmd->ld);
/* Check duplicate preimage (unlikely unless they specified it!) */
if (invoices_find_by_rhash(wallet->invoices,
&inv_dbid, &info->b11->payment_hash)) {
return command_fail(info->cmd,
INVOICE_PREIMAGE_ALREADY_EXISTS,
"preimage already used");
}
if (!invoices_create(wallet->invoices,
&inv_dbid,
info->b11->msat,
info->label,
info->b11->expiry,
b11enc,
info->b11->description,
info->b11->features,
&info->payment_preimage,
&info->b11->payment_hash,
NULL)) {
return command_fail(info->cmd, INVOICE_LABEL_ALREADY_EXISTS,
"Duplicate label '%s'",
info->label->s);
}
if (info->cmd->ld->unified_invoices && info->b11->fallbacks && !info->custom_fallbacks) {
for (size_t i = 0; i < tal_count(info->b11->fallbacks); i++) {
const u8 *fallback_script = info->b11->fallbacks[i];
invoices_create_fallback(wallet->invoices, inv_dbid, fallback_script);
}
}
/* Get details */
details = invoices_get_details(info, wallet->invoices, inv_dbid);
response = json_stream_success(info->cmd);
json_add_sha256(response, "payment_hash", &details->rhash);
json_add_u64(response, "expires_at", details->expiry_time);
json_add_string(response, "bolt11", details->invstring);
invoice_secret(&details->r, &payment_secret);
json_add_secret(response, "payment_secret", &payment_secret);
json_add_u64(response, "created_index", details->created_index);
notify_invoice_creation(info->cmd->ld, info->b11->msat,
info->payment_preimage, info->label);
if (warning_no_listincoming)
json_add_string(response, "warning_listincoming",
"No listincoming command available, cannot add routehints to invoice");
if (warning_mpp)
json_add_string(response, "warning_mpp",
"The invoice is only payable by MPP-capable payers.");
if (warning_capacity)
json_add_string(response, "warning_capacity",
"Insufficient incoming channel capacity to pay invoice");
if (warning_deadends)
json_add_string(response, "warning_deadends",
"Insufficient incoming capacity, once dead-end peers were excluded");
if (warning_offline)
json_add_string(response, "warning_offline",
"Insufficient incoming capacity, once offline peers were excluded");
if (warning_private_unused)
json_add_string(response, "warning_private_unused",
"Insufficient incoming capacity, once private channels were excluded (try exposeprivatechannels=true?)");
if (info->cmd->ld->unified_invoices && info->custom_fallbacks)
json_add_string(response, "warning_custom_fallbacks",
"WARNING: Not tracking on-chain payments for custom fallback addresses");
return command_success(info->cmd, response);
}
bolt11_encode_ and bolt11_encode
bolt11_encode_ in common/bolt11.h
/* Encodes and signs, even if it's nonsense. */
char *bolt11_encode_(const tal_t *ctx,
const struct bolt11 *b11, bool n_field,
bool (*sign)(const u5 *u5bytes,
const u8 *hrpu8,
secp256k1_ecdsa_recoverable_signature *rsig,
void *arg),
void *arg);
#define bolt11_encode(ctx, b11, n_field, sign, arg) \
bolt11_encode_((ctx), (b11), (n_field), \
typesafe_cb_preargs(bool, void *, (sign), (arg), \
const u5 *, \
const u8 *, \
secp256k1_ecdsa_recoverable_signature *rsig), \
(arg))
Source Code
json_my_invoice (in lightningd/invoice.c)
static struct command_result *json_my_invoice(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct amount_msat *msatoshi_val;
const char *desc_val;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("amount_msat", param_msat, &msatoshi_val),
p_req("description", param_escaped_string, &desc_val),
NULL))
return command_param_failed();
/* b11->payment_hash = rhash; */
struct bolt11 *b11 = tal(cmd, struct bolt11);
b11->msat = tal_dup(cmd, struct amount_msat, msatoshi_val);
b11->description = tal_steal(cmd, desc_val);
b11->timestamp = time_now().ts.tv_sec;
b11->chain = chainparams;
struct sha256 rhash;
struct secret payment_secret;
struct preimage payment_preimage;
randombytes_buf(&payment_preimage, sizeof(payment_preimage));
sha256(&rhash, &payment_preimage, sizeof(payment_preimage));
invoice_secret(&payment_preimage, &payment_secret);
b11->payment_hash = rhash;
b11->payment_secret = tal_dup(cmd, struct secret, &payment_secret);
char *b11enc;
/* b11enc = "lnbcrt...."; */
b11enc = my_bolt11_encode(cmd, b11, hsm_sign_b11, cmd->ld);
response = json_stream_success(cmd);
json_add_string(response, "bolt11", b11enc);
json_add_amount_msat(response, "msat", *b11->msat);
json_add_string(response, "description", b11->description);
json_add_sha256(response, "payment_hash", &b11->payment_hash);
json_add_secret(response, "payment_secret", b11->payment_secret);
json_add_string(response, "network_name", b11->chain->network_name);
json_add_string(response, "lightning_hrp", b11->chain->lightning_hrp);
json_add_u64(response, "timestamp", b11->timestamp);
return command_success(cmd, response);
}
static const struct json_command my_invoice_command = {
"my-invoice",
"clnlive",
json_my_invoice,
"my-invoice clnlive #17"};
AUTODATA(json_command, &my_invoice_command);
my_bolt11_encode_ (in common/bolt11.c)
char *my_bolt11_encode_(const tal_t *ctx,
const struct bolt11 *b11,
bool (*sign)(const u5 *u5bytes,
const u8 *hrpu8,
secp256k1_ecdsa_recoverable_signature *rsig,
void *arg),
void *arg)
{
u5 *data = tal_arr(tmpctx, u5, 0);
char *hrp, *output;
u64 amount;
secp256k1_ecdsa_recoverable_signature rsig;
u8 sig_and_recid[65];
u8 *hrpu8;
int recid;
/* BOLT #11:
*
* A writer:
* - MUST encode `prefix` using the currency required for successful payment.
* - if a specific minimum `amount` is required for successful payment:
* - MUST include that `amount`.
* - MUST encode `amount` as a positive decimal integer with no leading 0s.
* - If the `p` multiplier is used the last decimal of `amount` MUST be `0`.
* - SHOULD use the shortest representation possible, by using the largest multiplier or omitting the multiplier.
*/
if (b11->msat) {
char postfix;
u64 msat = b11->msat->millisatoshis; /* Raw: best-multiplier calc */
if (msat % MSAT_PER_BTC == 0) {
postfix = '\0';
amount = msat / MSAT_PER_BTC;
} else {
size_t i;
for (i = 0; i < ARRAY_SIZE(multipliers)-1; i++) {
if (!(msat * 10 % multipliers[i].m10))
break;
}
postfix = multipliers[i].letter;
amount = msat * 10 / multipliers[i].m10;
}
hrp = tal_fmt(tmpctx, "ln%s%"PRIu64"%c",
b11->chain->lightning_hrp, amount, postfix);
} else
hrp = tal_fmt(tmpctx, "ln%s", b11->chain->lightning_hrp);
/* */
/* BOLT #11:
*
* 1. `timestamp`: seconds-since-1970 (35 bits, big-endian)
* 1. zero or more tagged parts
* 1. `signature`: Bitcoin-style signature of above (520 bits)
*/
push_varlen_uint(&data, b11->timestamp, 35);
encode_p(&data, &b11->payment_hash);
encode_s(&data, b11->payment_secret);
encode_d(&data, b11->description);
/* Need exact length here */
hrpu8 = tal_dup_arr(tmpctx, u8, (const u8 *)hrp, strlen(hrp), 0);
if (!sign(data, hrpu8, &rsig, arg))
return NULL;
secp256k1_ecdsa_recoverable_signature_serialize_compact(
secp256k1_ctx,
sig_and_recid,
&recid,
&rsig);
sig_and_recid[64] = recid;
bech32_push_bits(&data, sig_and_recid, sizeof(sig_and_recid) * CHAR_BIT);
output = tal_arr(ctx, char, strlen(hrp) + tal_count(data) + 8);
if (!bech32_encode(output, hrp, data, tal_count(data), (size_t)-1,
BECH32_ENCODING_BECH32))
output = tal_free(output);
return output;
}
my_bolt11_encode_ and my_bolt11_encode (in common/bolt11.h)
char *my_bolt11_encode_(const tal_t *ctx,
const struct bolt11 *b11,
bool (*sign)(const u5 *u5bytes,
const u8 *hrpu8,
secp256k1_ecdsa_recoverable_signature *rsig,
void *arg),
void *arg);
#define my_bolt11_encode(ctx, b11, sign, arg) \
my_bolt11_encode_((ctx), (b11), \
typesafe_cb_preargs(bool, void *, (sign), (arg), \
const u5 *, \
const u8 *, \
secp256k1_ecdsa_recoverable_signature *rsig), \
(arg))