Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ jobs:
if: |
github.event.action != 'edited' ||
contains(github.event.pull_request.body, 'Changelog')
env:
BOLTDIR: bolts
strategy:
fail-fast: true
steps:
Expand Down Expand Up @@ -92,8 +90,6 @@ jobs:
TEST_NETWORK: ${{ matrix.TEST_NETWORK }}
run: |
bash -x .github/scripts/setup.sh
# We're going to check BOLT quotes, so get the latest version
git clone https://github.com/lightning/bolts.git ../${BOLTDIR}
- name: Configure
run: ./configure --enable-debugbuild --enable-rust
- name: Check source
Expand Down Expand Up @@ -199,6 +195,8 @@ jobs:
runs-on: ubuntu-24.04
needs:
- compile
env:
BOLTDIR: bolts
strategy:
matrix:
CFG: [compile-gcc]
Expand All @@ -224,6 +222,8 @@ jobs:
TEST_NETWORK: ${{ matrix.TEST_NETWORK }}
run: |
bash -x .github/scripts/setup.sh
# We're going to check BOLT quotes, so get the latest version
git clone https://github.com/lightning/bolts.git ../${BOLTDIR}

- name: Download build
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -732,7 +732,7 @@ jobs:

- name: Test
env:
PYTEST_OPTS: ${{ env.PYTEST_OPTS_BASE }} --test-group-random-seed=42
PYTEST_OPTS: ${{ env.PYTEST_OPTS_BASE }} --test-group-random-seed=42
run: |
sg wireshark "uv run eatmydata pytest tests/ -n $(($(nproc) + 1)) ${PYTEST_OPTS} --test-group=${{ matrix.GROUP }} --test-group-count=${{ matrix.GROUP_COUNT }}"
- name: Upload test results
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ coverage
docs/python
ccan/config.h
__pycache__
config.vars
config.vars*
monkeytype.sqlite3

# Ignore some generated binaries
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CCANDIR := ccan

# Where we keep the BOLT RFCs
BOLTDIR := ../bolts/
DEFAULT_BOLTVERSION := 68881992b97f20aca29edf7a4d673b8e6a70379a
DEFAULT_BOLTVERSION := a3772650d8ebc06acf457fcadf97968ebfc4dfff
# Can be overridden on cmdline.
BOLTVERSION := $(DEFAULT_BOLTVERSION)

Expand Down
29 changes: 21 additions & 8 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -5936,19 +5936,32 @@ static void peer_reconnect(struct peer *peer,
"next_funding_txid not recognized.");
}

/* "none of those channel_reestablish messages contain
* my_current_funding_locked or next_funding for a splice transaction" */
bool is_splice_active = local_next_funding
|| peer->splice_state->locked_ready[LOCAL]
|| remote_next_funding
|| (recv_tlvs
&& recv_tlvs->my_current_funding_locked
&& !bitcoin_txid_eq(
&recv_tlvs->my_current_funding_locked->my_current_funding_locked_txid,
&peer->channel->funding.txid));

/* BOLT #2:
*
* - if `next_commitment_number` is 1 in both the
* `channel_reestablish` it sent and received:
* `channel_reestablish` it sent and received, and none of those
* `channel_reestablish` messages contain `my_current_funding_locked` or
* `next_funding` for a splice transaction:
* - MUST retransmit `channel_ready`.
* - otherwise:
* - MUST NOT retransmit `channel_ready`, but MAY send
* `channel_ready` with a different `short_channel_id`
* `alias` field.
* - MUST NOT retransmit `channel_ready`, but MAY send `channel_ready` with
* a different `short_channel_id` `alias` field.
*/
if (peer->channel_ready[LOCAL]
&& peer->next_index[LOCAL] == 1
&& next_commitment_number == 1) {
&& next_commitment_number == 1
&& !is_splice_active) {
struct tlv_channel_ready_tlvs *tlvs = tlv_channel_ready_tlvs_new(tmpctx);

tlvs->short_channel_id = &peer->local_alias;
Expand Down Expand Up @@ -6072,9 +6085,9 @@ static void peer_reconnect(struct peer *peer,
/* BOLT #2:
*
* - otherwise:
* - if `next_commitment_number` is not 1 greater than the
* commitment number of the last `commitment_signed` message the
* receiving node has sent:
* - if `next_commitment_number` is not equal to the
* commitment number of the next `commitment_signed` that the
* receiving node would send:
* - SHOULD send an `error` and fail the channel.
*/
} else if (next_commitment_number != peer->next_index[REMOTE])
Expand Down
14 changes: 13 additions & 1 deletion common/bech32_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ bool from_bech32_charset(const tal_t *ctx,
u5 *u5data;
const char *sep;
bool upper = false, lower = false;
size_t datalen;
size_t datalen, nbits, trailing;

sep = memchr(bech32, '1', bech32_len);
if (!sep)
Expand All @@ -94,6 +94,18 @@ bool from_bech32_charset(const tal_t *ctx,
if (upper && lower)
goto fail;

/* Padding: converting N 5-bit groups to bytes leaves (N*5 % 8) trailing
* bits. These must be zero and fewer than 5 (otherwise a full 5-bit
* group is wasted as padding, which is invalid). */
nbits = datalen * 5;
trailing = nbits % 8;
if (trailing >= 5)
goto fail;
for (size_t i = nbits - trailing; i < nbits; i++) {
if (get_u5_bit(u5data, i))
goto fail;
}

*data = tal_arr(ctx, u8, 0);
bech32_pull_bits(data, u5data, tal_bytelen(u5data) * 5);
tal_free(u5data);
Expand Down
29 changes: 14 additions & 15 deletions common/bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ static const char *decode_p(struct bolt11 *b11,
/* BOLT #11:
*
* A reader...
* - MUST fail the payment if any mandatory field (`p`, `h`, `s`, `n`)
* - MUST fail the payment if any field with fixed `data_length` (`p`, `h`, `s`, `n`)
* does not have the correct length (52, 52, 52, 53).
*/
return pull_expected_length(b11, hu5, data, field_len, 52, 'p',
Expand Down Expand Up @@ -239,7 +239,7 @@ static const char *decode_h(struct bolt11 *b11,
/* BOLT #11:
*
* A reader...
* - MUST fail the payment if any mandatory field (`p`, `h`, `s`, `n`)
* - MUST fail the payment if any field with fixed `data_length` (`p`, `h`, `s`, `n`)
* does not have the correct length (52, 52, 52, 53). */
err = pull_expected_length(b11, hu5, data, field_len, 52, 'h',
have_h, &hash);
Expand Down Expand Up @@ -324,7 +324,7 @@ static const char *decode_n(struct bolt11 *b11,
/* BOLT #11:
*
* A reader...
* - MUST fail the payment if any mandatory field (`p`, `h`, `s`, `n`)
* - MUST fail the payment if any field with fixed `data_length` (`p`, `h`, `s`, `n`)
* does not have the correct length (52, 52, 52, 53). */
err = pull_expected_length(b11, hu5, data, field_len, 53, 'n', have_n,
&b11->receiver_id.k);
Expand Down Expand Up @@ -360,7 +360,7 @@ static const char *decode_s(struct bolt11 *b11,
/* BOLT #11:
*
* A reader...
* - MUST fail the payment if any mandatory field (`p`, `h`, `s`, `n`)
* - MUST fail the payment if any field with fixed `data_length` (`p`, `h`, `s`, `n`)
* does not have the correct length (52, 52, 52, 53). */
err = pull_expected_length(b11, hu5, data, field_len, 52, 's',
have_s, &secret);
Expand Down Expand Up @@ -876,7 +876,7 @@ struct bolt11 *bolt11_decode_nosig(const tal_t *ctx, const char *str,
*
* 1. `timestamp`: seconds-since-1970 (35 bits, big-endian)
* 1. zero or more tagged parts
* 1. `signature`: Bitcoin-style signature of above (520 bits)
* 1. `signature`: compact ECDSA/secp256k1 signature of the above (520 bits: 64-byte R||S + 1-byte recovery id)
*/
err = pull_uint(&hu5, &data, &data_len, &b11->timestamp, 35, false);
if (err)
Expand Down Expand Up @@ -999,13 +999,12 @@ struct bolt11 *bolt11_decode(const tal_t *ctx, const char *str,

/* BOLT #11:
*
* A writer...MUST set `signature` to a valid 512-bit
* secp256k1 signature of the SHA2 256-bit hash of the
* human-readable part, represented as UTF-8 bytes,
* concatenated with the data part (excluding the signature)
* with 0 bits appended to pad the data to the next byte
* boundary, with a trailing byte containing the recovery ID
* (0, 1, 2, or 3).
* A writer...MUST set `signature` to a valid compact ECDSA signature over
* secp256k1 of the SHA-256 hash of: the human-readable
* part (as UTF-8 bytes) concatenated with the data
* part (excluding the signature), with 0 bits appended to pad to a byte
* boundary. The signature is encoded as 64 bytes (R || S), followed by a
* trailing 1-byte recovery id in {0,1,2,3}.
*/
data_len = tal_count(sigdata);
err = pull_bits(NULL, &sigdata, &data_len, sig_and_recid, 520, false);
Expand All @@ -1031,7 +1030,7 @@ struct bolt11 *bolt11_decode(const tal_t *ctx, const char *str,
* A reader... MUST check that the `signature` is valid (see
* the `n` tagged field specified below). ... A reader...
* MUST use the `n` field to validate the signature instead of
* performing signature recovery.
* performing public-key recovery.
*/
if (!have_n) {
struct pubkey k;
Expand All @@ -1040,7 +1039,7 @@ struct bolt11 *bolt11_decode(const tal_t *ctx, const char *str,
&sig,
(const u8 *)&hash))
return decode_fail(b11, fail,
"signature recovery failed");
"public-key recovery failed");
node_id_from_pubkey(&b11->receiver_id, &k);
} else {
struct pubkey k;
Expand Down Expand Up @@ -1314,7 +1313,7 @@ char *bolt11_encode_(const tal_t *ctx,
*
* 1. `timestamp`: seconds-since-1970 (35 bits, big-endian)
* 1. zero or more tagged parts
* 1. `signature`: Bitcoin-style signature of above (520 bits)
* 1. `signature`: compact ECDSA/secp256k1 signature of the above (520 bits: 64-byte R||S + 1-byte recovery id)
*/
push_varlen_uint(&data, b11->timestamp, 35);

Expand Down
34 changes: 32 additions & 2 deletions common/bolt12.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
#include <inttypes.h>
#include <time.h>

/* If chains is NULL, max_num_chains is ignored */
/* If chains is NULL, max_num_chains is ignored.
* If must_be_chain is NULL, only structural validity is checked. */
bool bolt12_chains_match(const struct bitcoin_blkid *chains,
size_t max_num_chains,
const struct chainparams *must_be_chain)
Expand All @@ -28,9 +29,16 @@ bool bolt12_chains_match(const struct bitcoin_blkid *chains,
* - if the node does not accept bitcoin invoices:
* - MUST NOT respond to the offer
* - otherwise: (`offer_chains` is set):
* - if the node does not accept invoices for any of the `chains`:
* - if the node does not accept invoices for at least one of the `chains`:
* - MUST NOT respond to the offer
*/
if (chains && max_num_chains == 0)
return false;

/* No specific chain required: structurally valid. */
if (!must_be_chain)
return true;

if (!chains) {
max_num_chains = 1;
chains = &chainparams_for_network("bitcoin")->genesis_blockhash;
Expand Down Expand Up @@ -183,6 +191,18 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
return NULL;
}

/* BOLT #12:
* - otherwise: (`offer_chains` is set):
* - if the node does not accept invoices for at least one of the `chains`:
* - MUST NOT respond to the offer
*/
for (size_t i = 0; i < tal_count(offer->fields); i++) {
if (offer->fields[i].numtype == 2 && offer->fields[i].length == 0) {
*fail = tal_strdup(ctx, "offer_chains must have at least one entry");
return tal_free(offer);
}
}

*fail = check_features_and_chain(ctx,
our_features, must_be_chain,
offer->offer_features,
Expand Down Expand Up @@ -222,6 +242,16 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
return tal_free(offer);
}

/* BOLT #12:
*
* - if `offer_amount` is set and is not greater than zero:
* - MUST NOT respond to the offer.
*/
if (offer->offer_amount && *offer->offer_amount == 0) {
*fail = tal_strdup(ctx, "offer_amount must be > 0");
return tal_free(offer);
}

/* BOLT #12:
*
* - if `offer_currency` is set and `offer_amount` is not set:
Expand Down
15 changes: 7 additions & 8 deletions common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,20 @@ struct feature_set *feature_set_dup(const tal_t *ctx,
* | 8/9 | `var_onion_optin` |... ASSUMED ...
* | 10/11 | `gossip_queries_ex` |... IN ...
* | 12/13 | `option_static_remotekey` |... ASSUMED ...
* | 14/15 | `payment_secret` |... IN9 ...
* | 14/15 | `payment_secret` |... ASSUMED ...
* | 16/17 | `basic_mpp` |... IN9 ...
* | 18/19 | `option_support_large_channel` |... IN ...
* | 22/23 | `option_anchors` |... IN ...
* | 24/25 | `option_route_blinding` |...IN9 ...
* | 22/23 | `option_anchors` |... INT ...
* | 24/25 | `option_route_blinding` |... IN9 ...
* | 26/27 | `option_shutdown_anysegwit` |... IN ...
* | 28/29 | `option_dual_fund` |... IN ...
* | 34/35 | `option_quiesce` |... IN ...
* | 38/39 | `option_onion_messages` |... IN ...
* | 42/43 | `option_provide_storage` |... IN ...
* | 44/45 | `option_channel_type` |... IN ...
* | 46/47 | `option_scid_alias` | ... IN ...
* | 48/49 | `option_payment_metadata` |... 9 ...
* | 50/51 | `option_zeroconf` | ... IN ...
* | 60/61 | `option_simple_close` |... IN ...
* | 44/45 | `option_channel_type` |... ASSUMED ...
* | 46/47 | `option_scid_alias` |... INT ...
* | 48/49 | `option_payment_metadata` |... 9 ...
* | 50/51 | `option_zeroconf` |... INT ...
* | 62/63 | `option_splice` |... IN ...
*/
#define OPT_DATA_LOSS_PROTECT 0
Expand Down
6 changes: 3 additions & 3 deletions common/interactivetx.c
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,9 @@ char *process_interactivetx_updates(const tal_t *ctx,
* BOLT #2:
* The receiving node: ...
* - MUST fail the negotiation if:...
* - the `prevtx` and `prevtx_vout` are
* identical to a previously added (and not
* removed) input's
* - `prevtx` and `prevtx_vout` are
* identical to a previously added (and not
* removed) input
*/
bitcoin_txid(tx, &outpoint.txid);
if (psbt_has_input(ictx->current_psbt, &outpoint))
Expand Down
7 changes: 2 additions & 5 deletions common/sphinx.c
Original file line number Diff line number Diff line change
Expand Up @@ -742,9 +742,8 @@ struct onionreply *create_onionreply(const tal_t *ctx,

/* BOLT #4:
*
* The node generating the error message (_erring node_) builds a return
* packet consisting of
* the following fields:
* The node generating the error message builds a _return packet_ consisting
* of the following fields:
*
* 1. data:
* * [`32*byte`:`hmac`]
Expand Down Expand Up @@ -790,8 +789,6 @@ struct onionreply *wrap_onionreply(const tal_t *ctx,
* The erring node then generates a new key, using the key type `ammag`.
* This key is then used to generate a pseudo-random stream, which is
* in turn applied to the packet using `XOR`.
*
* The obfuscation step is repeated by every hop along the return path.
*/
subkey_from_hmac("ammag", shared_secret, &key);
result->contents = tal_dup_talarr(result, u8, reply->contents);
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-bolt11.c
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ int main(int argc, char *argv[])
* > lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2
*/
assert(!bolt11_decode(tmpctx, "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2", NULL, NULL, NULL, &fail));
assert(streq(fail, "signature recovery failed"));
assert(streq(fail, "public-key recovery failed"));

/* BOLT #11:
* > ### String is too short.
Expand Down
12 changes: 11 additions & 1 deletion common/test/run-bolt12-encode-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -429,15 +429,25 @@ int main(int argc, char *argv[])
/* BOLT #12:
* - if `offer_amount` is set and `offer_description` is not set:
* - MUST NOT respond to the offer.
* - if `offer_amount` is set and is not greater than zero:
* - MUST NOT respond to the offer.
* - if `offer_currency` is set and `offer_amount` is not set:
* - MUST NOT respond to the offer.
* - if neither `offer_issuer_id` nor `offer_paths` are set:
* - MUST NOT respond to the offer.
*/
offer->offer_amount = tal(offer, u64);
*offer->offer_amount = 10000;

offer->offer_description = NULL;
print_invalid_offer(offer, "Missing offer_description and offer_amount");
print_invalid_offer(offer, "Missing offer_description");
offer->offer_description = tal_utf8(tmpctx, "Test vectors");

offer->offer_amount = tal(offer, u64);
*offer->offer_amount = 0;
print_invalid_offer(offer, "offer_amount must be > 0");
offer->offer_amount = NULL;

offer->offer_currency = tal_utf8(offer, "USD");
print_invalid_offer(offer, "Missing offer_amount with offer_currency");
offer->offer_currency = NULL;
Expand Down
Loading
Loading