Core Lightning implementation of BOLT #11 invoices - part 8

LIVE #24March 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))

Resources