From c6f18ff705ddbb615f3c9909b17d08accdc6bb07 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 30 Apr 2026 10:12:12 +0930 Subject: [PATCH] global: more missing BOLT quotes. Signed-off-by: Rusty Russell --- channeld/channeld.c | 12 +++++++++++ closingd/closingd.c | 9 ++++++++ common/bech32_util.c | 11 ++++++++++ common/bolt11.c | 7 ++++++ common/channel_id.c | 9 ++++++++ common/close_tx.c | 11 ++++++++++ gossipd/gossipd.c | 9 ++++++++ lightningd/channel.c | 8 +++++++ lightningd/closing_control.c | 3 +++ lightningd/invoice.c | 6 ++++++ lightningd/lightningd.c | 15 +++++++++++++ lightningd/onchain_control.c | 24 +++++++++++++++++++++ lightningd/peer_control.c | 8 +++++++ lightningd/peer_htlcs.c | 6 ++++++ onchaind/onchaind.c | 42 ++++++++++++++++++++++++++++-------- openingd/openingd.c | 11 +++++++--- plugins/fetchinvoice.c | 18 ++++++++++++++++ plugins/offers.c | 13 +++++++++++ plugins/offers_inv_hook.c | 4 ++++ wallet/walletrpc.c | 7 +++++- wire/tlvstream.c | 7 ++++++ 21 files changed, 227 insertions(+), 13 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index ad4b67347..6fa715803 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -658,9 +658,21 @@ static void handle_peer_add_htlc(struct peer *peer, const u8 *msg) "Bad peer_add_htlc %s", tal_hex(msg, msg)); } + /* BOLT #2: + * - if the sender did not previously acknowledge the commitment of that HTLC: + * - MUST ignore a repeated `id` value after a reconnection. + */ + /* We do this is a subtle way: we only save the HTLC to the db once we + * have received commitment_signed, which is when we send the + * acknowledgement. So if we disconnect, we restart channeld which + * doesn't know about the HTLC, thus rexmits are fine. + * + * If we got the commitment_signed, we increment the reestablish fields + * so they know not to send it again. */ /* BOLT #2: * - MUST allow multiple HTLCs with the same `payment_hash`. */ + /* We do: the key is the id, not the payment_hash */ add_err = channel_add_htlc(peer->channel, REMOTE, id, amount, cltv_expiry, &payment_hash, onion_routing_packet, diff --git a/closingd/closingd.c b/closingd/closingd.c index e3c22ecee..fdf2c2634 100644 --- a/closingd/closingd.c +++ b/closingd/closingd.c @@ -59,6 +59,15 @@ static struct bitcoin_tx *close_tx(const tal_t *ctx, struct bitcoin_tx *tx; struct amount_sat out_minus_fee[NUM_SIDES]; + /* BOLT #3: + * ## Legacy Closing Transaction + *... + * ### Requirements + * + * Each node offering a signature: + *... + * - MUST subtract the fee given by `fee_satoshis` from the output to the funder. + */ out_minus_fee[LOCAL] = out[LOCAL]; out_minus_fee[REMOTE] = out[REMOTE]; if (!amount_sat_sub(&out_minus_fee[opener], out[opener], fee)) diff --git a/common/bech32_util.c b/common/bech32_util.c index bae0f1e81..b72777e7d 100644 --- a/common/bech32_util.c +++ b/common/bech32_util.c @@ -105,6 +105,17 @@ fail: return false; } +/* BOLT #12: + * # Encoding + *... + * ## Requirements + * Writers of a bolt12 string: + * - MUST either use all lowercase or all UPPERCASE. + * - SHOULD use uppercase for QR codes. + * - SHOULD use lower case otherwise. + * - MAY use `+`, optionally followed by whitespace, to separate large bolt12 strings. + */ +/* We use lower case, and we leave it to the caller to upcase (and +-break) if it wants */ char *to_bech32_charset(const tal_t *ctx, const char *hrp, const u8 *data) { diff --git a/common/bolt11.c b/common/bolt11.c index d15996804..bd15e3af1 100644 --- a/common/bolt11.c +++ b/common/bolt11.c @@ -1407,6 +1407,13 @@ char *bolt11_encode_(const tal_t *ctx, bech32_push_bits(&data, sig_and_recid, sizeof(sig_and_recid) * CHAR_BIT); + /* BOLT #11: + * A writer: + * - MUST encode the payment request in Bech32 (see BIP-0173) + * - SHOULD use upper case for QR codes (see BIP-0173) + * - MAY exceed the 90-character limit specified in BIP-0173. + */ + /* We let the user upcase if they want */ 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)) diff --git a/common/channel_id.c b/common/channel_id.c index 5caffe233..dea8ce727 100644 --- a/common/channel_id.c +++ b/common/channel_id.c @@ -6,6 +6,15 @@ #include #include +/* BOLT #2: + * ### The `funding_signed` Message + *... + * #### Requirements + *... + * The sender MUST set: + * - `channel_id` by exclusive-OR of the `funding_txid` and the + * `funding_output_index` from the `funding_created` message. + */ void derive_channel_id(struct channel_id *channel_id, const struct bitcoin_outpoint *outpoint) { diff --git a/common/close_tx.c b/common/close_tx.c index 8d6805b79..4bd985bd5 100644 --- a/common/close_tx.c +++ b/common/close_tx.c @@ -23,6 +23,17 @@ struct bitcoin_tx *create_close_tx(const tal_t *ctx, struct amount_sat total_out; u8 *script; + /* BOLT #3: + * ## Legacy Closing Transaction + *... + * ### Requirements + * + * Each node offering a signature: + * - MUST round each output down to whole satoshis. + * - MUST subtract the fee given by `fee_satoshis` from the output to the funder. + * - MUST remove any output below its own `dust_limit_satoshis`. + * - MAY eliminate its own output. + */ assert(amount_sat_add(&total_out, to_us, to_them)); assert(amount_sat_less_eq(total_out, funding_sats)); diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index a2c6fdf26..b9f454249 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -495,6 +495,15 @@ static void inject_gossip(struct daemon *daemon, const u8 *msg) /*~ This is where lightningd tells us that a channel's funding transaction has * been spent. */ +/* BOLT #7: + * ## Pruning the Network View + *... + * ### Requirements + * + * A node: + * - SHOULD monitor the funding transactions in the blockchain, to identify + * channels that are being closed. + */ static void handle_outpoints_spent(struct daemon *daemon, const u8 *msg) { struct short_channel_id *scids; diff --git a/lightningd/channel.c b/lightningd/channel.c index 2254d0511..62d6d995c 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -1088,6 +1088,14 @@ static void channel_fail_perm(struct channel *channel, why); } + /* BOLT #5: + * A node: + * - if a *local commitment transaction* has NOT ever contained a `to_local` + * or HTLC output: + * - MAY simply forget the channel. + */ + /* FIXME: We only implement a subset of this; we keep waiting + * as long as it was finished opening. */ if (channel_state_open_uncommitted(channel->state)) { delete_channel(channel, false); return; diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index b80821ff8..9d1859c71 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -433,6 +433,9 @@ void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) } /* BOLT #3: + * ## Legacy Closing Transaction + *... + * ### Requirements * * Each node offering a signature: * - MUST round each output down to whole satoshis. diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 28bc54ce9..f214b407b 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -1235,6 +1235,12 @@ static struct command_result *json_invoice(struct command *cmd, info->b11->description_hash = NULL; info->b11->payment_secret = tal_dup(info->b11, struct secret, &payment_secret); + /* BOLT #11: + * + * A writer: + * - MUST set the `9` field to a feature vector compliant with the + * [BOLT 9 origin node requirements](09-features.md#requirements). + */ info->b11->features = tal_dup_talarr(info->b11, u8, cmd->ld->our_features ->bits[BOLT11_FEATURE]); diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 7dba11539..3e21caa69 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -890,6 +890,21 @@ static struct feature_set *default_features(const tal_t *ctx) * option_data_loss_protect (all but 11 nodes) * option_static_remotekey (all but 16 nodes) */ + /* BOLT #9: + * The origin node: + * * If it supports a feature above, SHOULD set the corresponding odd + * bit in all feature fields indicated by the Context column unless + * indicated that it must set the even feature bit instead. + * * If it requires a feature above, MUST set the corresponding even + * feature bit in all feature fields indicated by the Context column, + * unless indicated that it must set the odd feature bit instead. + * * MUST NOT set feature bits it does not support. + * * MUST NOT set feature bits in fields not specified by the table above. + * * MUST NOT set both the optional and mandatory bits. + * * MUST set all transitive feature dependencies. + * * MUST support: + * * `var_onion_optin` + */ struct feature_set *ret = NULL; static const u32 features[] = { COMPULSORY_FEATURE(OPT_DATA_LOSS_PROTECT), diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index b6dfc4dde..4457f82b5 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -932,6 +932,15 @@ static struct bitcoin_tx *onchaind_tx_unsigned(const tal_t *ctx, weight = bitcoin_tx_weight(tx) + 1 + 3 + 73 + 0 + tal_count(info->wscript); weight += elements_tx_overhead(chainparams, 1, 1); + /* BOLT #5: + * A node which broadcasts an HTLC-success or HTLC-timeout transaction for a + * commitment transaction: + * - if `option_anchors` applies: + * - MUST combine it with inputs contributing sufficient fee to ensure timely + * inclusion in a block. + * - MAY combine it with other transactions. + */ + /* FIXME: We don't combine! */ block_target = info->deadline_block; for (;;) { u32 feerate; @@ -1305,6 +1314,21 @@ static void create_onchain_tx(struct channel *channel, welements))); } +/* BOLT #5: + * + * - if `option_anchors` applies: + * - MAY use a single transaction to *resolve* all the outputs. + * - if confirmation doesn't happen before reaching `security_delay` blocks from + * expiry: + * - SHOULD *resolve* revoked outputs in their own, separate penalty transactions. A previous + * penalty transaction claiming multiple revoked outputs at once may be blocked from confirming + * because of a transaction pinning attack. + * - otherwise: + * - MAY use a single transaction to *resolve* all the outputs. + * - MUST handle its transactions being invalidated by HTLC transactions. + */ +/* FIXME: we always use one transaction per HTLC, even when we have + * plenty of time and could combine them. */ static void handle_onchaind_spend_to_us(struct channel *channel, const u8 *msg) { diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index ddcdb27e9..ed0065181 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -228,6 +228,14 @@ u8 *p2tr_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx) return scriptpubkey_p2tr(ctx, &shutdownkey); } +/* BOLT #2: + * A node: + * - MUST NOT broadcast old (revoked) commitment transactions, + * - Note: doing so will allow the other node to seize all channel funds. + * - SHOULD NOT sign commitment transactions, unless it's about to broadcast + * them (due to a failed connection), + * - Note: this is to reduce the above risk. + */ static struct bitcoin_tx *sign_last_tx(const tal_t *ctx, const struct channel *channel, const struct bitcoin_tx *last_tx, diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index a9972627a..53b01ede1 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -2625,6 +2625,12 @@ void peer_got_revoke(struct channel *channel, const u8 *msg) badonions = tal_arrz(msg, enum onion_wire, tal_count(changed)); failmsgs = tal_arrz(msg, u8 *, tal_count(changed)); for (i = 0; i < tal_count(changed); i++) { + /* BOLT #2: + * A node: + * - until an incoming HTLC has been irrevocably committed: + * - MUST NOT offer the corresponding outgoing HTLC + * (`update_add_htlc`) in response to that incoming HTLC. + */ /* If we're doing final accept, we need to forward */ if (changed[i].newstate == RCVD_ADD_ACK_REVOCATION) { peer_accepted_htlc(failmsgs, diff --git a/onchaind/onchaind.c b/onchaind/onchaind.c index e326afca1..f7e3dacb7 100644 --- a/onchaind/onchaind.c +++ b/onchaind/onchaind.c @@ -961,6 +961,10 @@ static void billboard_update(struct tracked_output **outs) output_type_name(best->output_type), best->depth); } +/* BOLT #5: + * - SHOULD extract the payment preimage from the transaction input witness, if + * it's not already known. + */ static void handle_htlc_onchain_fulfill(struct tracked_output *out, const struct tx_parts *tx_parts, const struct bitcoin_outpoint *htlc_outpoint) @@ -1376,7 +1380,14 @@ static void tx_new_depth(struct tracked_output **outs, { size_t i; - /* Special handling for commitment tx reaching depth */ + /* BOLT #5: + * - for any committed HTLC that does NOT have an output in this + * commitment transaction: + *... + * - otherwise: + * - once the commitment transaction has reached reasonable depth: + * - MUST fail the corresponding incoming HTLC (if any). + */ if (bitcoin_txid_eq(&outs[0]->resolved->txid, txid) && depth >= reasonable_depth && missing_htlc_msgs) { @@ -2036,6 +2047,11 @@ static enum side matches_direction(const size_t *matches, static void note_missing_htlcs(u8 **htlc_scripts, const struct htlcs_info *htlcs_info) { + /* BOLT #5: + * - for any committed HTLC that does NOT have an output in this + * commitment transaction: + */ + /* See onchain_control.c for the rest of the quote! */ for (size_t i = 0; i < tal_count(htlcs_info->htlcs); i++) { u8 *msg; @@ -2161,9 +2177,18 @@ static void handle_our_unilateral(const struct tx_parts *tx, /* BOLT #5: * - * In this case, a node discovers its *local commitment transaction*, - * which *resolves* the funding transaction output. + * A node: + * - upon discovering its *local commitment transaction*: + * - SHOULD spend the `to_local` output to a convenient address. + * - MUST wait until the `OP_CHECKSEQUENCEVERIFY` delay has passed (as + * specified by the remote node's `to_self_delay` field) before spending the + * output. + * - Note: if the output is spent (as recommended), the output is *resolved* + * by the spending transaction, otherwise it is considered *resolved* by the + * commitment transaction itself. */ + /* We do NOT spend our unilateral, we remember how to spend it + * in the wallet, thus it's immediately resolved. */ resolved_by_other(outs[0], &tx->txid, OUR_UNILATERAL); /* Figure out what delayed to-us output looks like */ @@ -2267,7 +2292,7 @@ static void handle_our_unilateral(const struct tx_parts *tx, && wally_tx_output_scripteq(tx->outputs[i], script[REMOTE])) { /* BOLT #5: - * + *... * - MAY ignore the `to_remote` output. * - Note: No action is required by the local * node, as `to_remote` is considered *resolved* @@ -2409,7 +2434,7 @@ static void handle_our_unilateral(const struct tx_parts *tx, * * - MUST handle HTLCs offered by itself as specified * in [HTLC Output Handling: Local Commitment, - * Local Offers] + * Local Offers](#htlc-output-handling-local-commitment-local-offers). */ out = new_tracked_output(&outs, &outpoint, tx_blockheight, @@ -2431,10 +2456,9 @@ static void handle_our_unilateral(const struct tx_parts *tx, NULL, NULL, remote_htlc_sigs); /* BOLT #5: - * - * - MUST handle HTLCs offered by the remote node - * as specified in [HTLC Output Handling: Local - * Commitment, Remote Offers] + * - MUST handle HTLCs offered by the remote node + * as specified in [HTLC Output Handling: Local + * Commitment, Remote Offers](#htlc-output-handling-local-commitment-remote-offers). */ /* Tells us which htlc to use */ which_htlc = resolve_their_htlc(out, matches, diff --git a/openingd/openingd.c b/openingd/openingd.c index cf63b3c28..cc51e1e67 100644 --- a/openingd/openingd.c +++ b/openingd/openingd.c @@ -410,15 +410,20 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags, their_mindepth); /* BOLT #2: - * - if `channel_type` is set, and `channel_type` was set in - * `open_channel`, and they are not equal types: - * - MUST fail the channel. + * - if `option_channel_type` was negotiated but the message doesn't + * include a `channel_type`: + * - MAY fail the channel. */ if (!accept_tlvs->channel_type) { negotiation_failed(state, "accept_channel without a channel_type"); } + /* BOLT #2: + * - if `channel_type` is set, and `channel_type` was set in + * `open_channel`, and they are not equal types: + * - MUST fail the channel. + */ /* Simple case: caller specified, don't allow any variants */ if (!featurebits_eq(accept_tlvs->channel_type, state->channel_type->features)) { negotiation_failed(state, diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 848f3504a..c8b003dd8 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -368,6 +368,16 @@ struct command_result *handle_invoice_onion_message(struct command *cmd, struct sent *sent; struct command_result *err; + /* BOLT #4: + * - otherwise (it is the final node): + * - if `path_id` is set and corresponds to a path the reader has previously published in a `reply_path`: + * - if the onion message is not a reply to that previous onion: + * - MUST ignore the onion message + * - otherwise (unknown or unset `path_id`): + * - if the onion message is a reply to an onion message which contained a `path_id`: + * - MUST respond (or not respond) exactly as if it did not send the initial onion message. + */ + /* We unmarshal the path_id into our secret: if it is NULL or wrong, we exit here */ sent = find_sent_by_secret(pathsecret); if (!sent) return NULL; @@ -771,6 +781,14 @@ static struct command_result *invreq_done(struct command *cmd, payload->invoice_request = tal_arr(payload, u8, 0); towire_tlv_invoice_request(&payload->invoice_request, sent->invreq); + /* BOLT #12: + * - if it chooses to send an invoice request, it sends an onion message: + * - if `offer_paths` is set: + * - MUST send the onion message via any path in `offer_paths` to the final + * `onion_msg_hop`.`blinded_node_id` in that path + * - otherwise: + * - MUST send the onion message to `offer_issuer_id` + */ /* Don't expect a reply message for cancel */ return send_message(cmd, sent, sent->invreq->invreq_recurrence_cancel ? false : true, diff --git a/plugins/offers.c b/plugins/offers.c index b2e8b722f..436a32f4e 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -288,6 +288,19 @@ static struct command_result *onion_message_recv(struct command *cmd, return res; } + /* BOLT #4: + * - otherwise (it is the final node): + * - if `path_id` is set and corresponds to a path the reader has previously published in a `reply_path`: + * - if the onion message is not a reply to that previous onion: + * - MUST ignore the onion message + * - otherwise (unknown or unset `path_id`): + * - if the onion message is a reply to an onion message which contained a `path_id`: + * - MUST respond (or not respond) exactly as if it did not send the initial onion message. + */ + /* FIXME: Technically, we don't meet the first half of this: we + * always do parsing and sanity checking before checking the + * secret (thus the path_id). But we ignore it *after* that. + */ replytok = json_get_member(buf, om, "reply_blindedpath"); if (replytok) { reply_path = json_to_blinded_path(cmd, buf, replytok); diff --git a/plugins/offers_inv_hook.c b/plugins/offers_inv_hook.c index 9de434c80..ff2014e33 100644 --- a/plugins/offers_inv_hook.c +++ b/plugins/offers_inv_hook.c @@ -257,6 +257,10 @@ struct command_result *handle_invoice(struct command *cmd, fmt_sha256(tmpctx, &inv->invreq_id)); } } else { + /* BOLT #12: + * - if `invreq_paths` is present: + * - MUST reject the invoice if it did not arrive via one of those paths. + */ /* Didn't use path. Was it supposed to? */ if (inv->inv->invreq_paths) { if (command_dev_apis(cmd)) diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 700367cc6..045ce7159 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -459,7 +459,12 @@ static struct command_result *json_listfunds(struct command *cmd, p = peer_node_id_map_next(cmd->ld->peers, &it)) { struct channel *c; list_for_each(&p->channels, c, list) { - /* We don't print out uncommitted channels */ + /* We don't print out uncommitted channels, which makes us meet this: */ + /* BOLT #2: + * The receiving node MUST NOT: + * - consider funds received, using `push_msat`, to be received + * until the funding transaction has reached sufficient depth. + */ if (channel_state_uncommitted(c->state)) continue; json_object_start(response, NULL); diff --git a/wire/tlvstream.c b/wire/tlvstream.c index 8afb7f2b4..8f276a725 100644 --- a/wire/tlvstream.c +++ b/wire/tlvstream.c @@ -203,6 +203,13 @@ bool fromwire_tlv(const u8 **cursor, size_t *max, } } + /* BOLT #1: + * - otherwise, if `type` is unknown: + * - if `type` is even: + * - MUST fail to parse the `tlv_stream`. + * - otherwise, if `type` is odd: + * - MUST discard the next `length` bytes. + */ if (!field.meta && !tlv_type_is_allowed(&field, extra_types)) { SUPERVERBOSE("unknown even"); if (err_type != NULL)