fuzz-tests: Make `fuzz-bolt12-invrequest-decode` roundrip
What changed, and why it matters
This commit only changes a fuzz test file. It extends an existing test so that after decoding a BOLT 12 invoice request, it re-encodes the result and decodes it again, then checks the two decoded versions match. There is no change to production code, no bug fix, and no security patch.
No action required. This is a test-only enhancement and does not affect deployed security posture.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies tests/fuzz/fuzz-bolt12-invrequest-decode.c. It adds deep equality helpers for tlv_invoice_request and related structs, then updates the fuzz harness to roundtrip decode->encode->decode and assert equality. The previous code only called invrequest_decode and discarded the result. No library or runtime code is altered.
Changed components
tests/fuzz/fuzz-bolt12-invrequest-decode.cInspect captured patch +187 / −3
diff --git a/tests/fuzz/fuzz-bolt12-invrequest-decode.c b/tests/fuzz/fuzz-bolt12-invrequest-decode.c
index 54bbc50e..45db0454 100644
--- a/tests/fuzz/fuzz-bolt12-invrequest-decode.c
+++ b/tests/fuzz/fuzz-bolt12-invrequest-decode.c
@@ -1,4 +1,5 @@
#include "config.h"
+#include <ccan/mem/mem.h>
#include <common/bolt12.h>
#include <common/utils.h>
#include <stddef.h>
@@ -7,12 +8,195 @@
const char *bech32_hrp = "lnr";
+static bool sciddir_or_pubkey_eq(const struct sciddir_or_pubkey *a,
+ const struct sciddir_or_pubkey *b)
+{
+ if (a->is_pubkey != b->is_pubkey)
+ return false;
+ if (a->is_pubkey)
+ return pubkey_eq(&a->pubkey, &b->pubkey);
+ else
+ return short_channel_id_dir_eq(&a->scidd, &b->scidd);
+}
+
+static bool recurrence_eq(const struct recurrence *a, const struct recurrence *b)
+{
+ return a->time_unit == b->time_unit && a->period == b->period;
+}
+
+static bool recurrence_paywindow_eq(const struct recurrence_paywindow *a,
+ const struct recurrence_paywindow *b)
+{
+ return a->seconds_before == b->seconds_before &&
+ a->proportional_amount == b->proportional_amount &&
+ a->seconds_after == b->seconds_after;
+}
+
+static bool recurrence_base_eq(const struct recurrence_base *a,
+ const struct recurrence_base *b)
+{
+ return a->start_any_period == b->start_any_period &&
+ a->basetime == b->basetime;
+}
+
+static bool bip340sig_eq(const struct bip340sig *a, const struct bip340sig *b)
+{
+ return !memcmp(a, b, sizeof(struct bip340sig));
+}
+
+static bool blinded_path_eq(const struct blinded_path *a, const struct blinded_path *b)
+{
+ if (!sciddir_or_pubkey_eq(&a->first_node_id, &b->first_node_id))
+ return false;
+ if (!pubkey_eq(&a->first_path_key, &b->first_path_key))
+ return false;
+ if (tal_count(a->path) != tal_count(b->path))
+ return false;
+ for (size_t i = 0; i < tal_count(a->path); i++) {
+ const struct blinded_path_hop *h1 = a->path[i];
+ const struct blinded_path_hop *h2 = b->path[i];
+ if (h1 == h2)
+ continue;
+ if (!h1 || !h2)
+ return false;
+ if (!pubkey_eq(&h1->blinded_node_id, &h2->blinded_node_id))
+ return false;
+ if (tal_bytelen(h1->encrypted_recipient_data) !=
+ tal_bytelen(h2->encrypted_recipient_data))
+ return false;
+ if (memcmp(h1->encrypted_recipient_data, h2->encrypted_recipient_data,
+ tal_bytelen(h1->encrypted_recipient_data)) != 0)
+ return false;
+ }
+ return true;
+}
+
+static bool invreq_bip_353_name_eq(const struct tlv_invoice_request_invreq_bip_353_name *a,
+ const struct tlv_invoice_request_invreq_bip_353_name *b)
+{
+ if (a == b)
+ return true;
+ if (!a || !b)
+ return false;
+ if (!memeq(a->name, tal_bytelen(a->name), b->name, tal_bytelen(b->name)))
+ return false;
+ if (!memeq(a->domain, tal_bytelen(a->domain), b->domain, tal_bytelen(b->domain)))
+ return false;
+ return true;
+}
+
+static bool tlv_invoice_request_eq(const struct tlv_invoice_request *a, const struct tlv_invoice_request *b)
+{
+
+#define PTR_EQ(field, eqfn) \
+do { \
+ if (a->field != b->field) { \
+ if (!a->field || !b->field) \
+ return false; \
+ if (!eqfn(a->field, b->field)) \
+ return false; \
+ } \
+} while (0)
+
+#define MEM_EQ(field) \
+do { \
+ if (a->field != b->field) { \
+ if (!a->field || !b->field) \
+ return false; \
+ if (tal_bytelen(a->field) != tal_bytelen(b->field)) \
+ return false; \
+ if (memcmp(a->field, b->field, tal_bytelen(a->field)) != 0) \
+ return false; \
+ } \
+} while (0)
+
+#define VAL_EQ(field) \
+do { \
+ if (a->field != b->field) { \
+ if (!a->field || !b->field) \
+ return false; \
+ if (*a->field != *b->field) \
+ return false; \
+ } \
+} while (0)
+
+#define ARR_EQ(field, eqfn) \
+do { \
+ if (a->field != b->field) { \
+ if (!a->field || !b->field) \
+ return false; \
+ if (tal_count(a->field) != tal_count(b->field)) \
+ return false; \
+ for (size_t i = 0; i < tal_count(a->field); i++) { \
+ if (!eqfn(&a->field[i], &b->field[i])) \
+ return false; \
+ } \
+ } \
+} while (0)
+
+#define PTR_ARR_EQ(field, eqfn) \
+do { \
+ if (a->field != b->field) { \
+ if (!a->field || !b->field) \
+ return false; \
+ if (tal_count(a->field) != tal_count(b->field)) \
+ return false; \
+ for (size_t i = 0; i < tal_count(a->field); i++) { \
+ if (!eqfn(a->field[i], b->field[i])) \
+ return false; \
+ } \
+ } \
+} while (0)
+
+ MEM_EQ(invreq_metadata);
+ ARR_EQ(offer_chains, bitcoin_blkid_eq);
+ MEM_EQ(offer_metadata);
+ MEM_EQ(offer_currency);
+ VAL_EQ(offer_amount);
+ MEM_EQ(offer_description);
+ MEM_EQ(offer_features);
+ VAL_EQ(offer_absolute_expiry);
+ PTR_ARR_EQ(offer_paths, blinded_path_eq);
+ MEM_EQ(offer_issuer);
+ VAL_EQ(offer_quantity_max);
+ PTR_EQ(offer_issuer_id, pubkey_eq);
+ PTR_EQ(offer_recurrence, recurrence_eq);
+ PTR_EQ(offer_recurrence_paywindow, recurrence_paywindow_eq);
+ VAL_EQ(offer_recurrence_limit);
+ PTR_EQ(offer_recurrence_base, recurrence_base_eq);
+ PTR_EQ(invreq_chain, bitcoin_blkid_eq);
+ VAL_EQ(invreq_amount);
+ MEM_EQ(invreq_features);
+ VAL_EQ(invreq_quantity);
+ PTR_EQ(invreq_payer_id, pubkey_eq);
+ MEM_EQ(invreq_payer_note);
+ PTR_ARR_EQ(invreq_paths, blinded_path_eq);
+ PTR_EQ(invreq_bip_353_name, invreq_bip_353_name_eq);
+ VAL_EQ(invreq_recurrence_counter);
+ VAL_EQ(invreq_recurrence_start);
+ PTR_EQ(signature, bip340sig_eq);
+
+ return true;
+}
+
void run(const u8 *data, size_t size)
{
- char *fail;
+ struct tlv_invoice_request *invreq, *decoded_invreq;
+ char *fail = NULL, *encoded_invreq;
+
+ invreq = invrequest_decode(tmpctx, (const char *)data, size,
+ /*feature_set=*/NULL, /*must_be_chain=*/NULL, &fail);
+ if (!invreq)
+ goto cleanup;
+
+ encoded_invreq = invrequest_encode(tmpctx, invreq);
- invrequest_decode(tmpctx, (const char *)data, size,
- /*feature_set=*/NULL, /*must_be_chain=*/NULL, &fail);
+ decoded_invreq = invrequest_decode(tmpctx, encoded_invreq, strlen(encoded_invreq),
+ NULL, NULL, &fail);
+ assert(!fail);
+ assert(decoded_invreq);
+ assert(tlv_invoice_request_eq(invreq, decoded_invreq));
+cleanup:
clean_tmpctx();
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.