From 557a744bb7213475c78cc78856bc07ae73aae925 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Fri, 11 Jul 2025 06:00:43 +0000 Subject: [PATCH] fuzz-tests: Make `fuzz-bolt12-offer-decode` roundrip Changelog-None: Currently, the `BOLT #12` offer parsing test only tests the offer decode function. Add a test for the encoding function as well by making the test roundtrip. --- tests/fuzz/fuzz-bolt12-offer-decode.c | 159 +++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 3 deletions(-) diff --git a/tests/fuzz/fuzz-bolt12-offer-decode.c b/tests/fuzz/fuzz-bolt12-offer-decode.c index d062b95a6..4ab229c6f 100644 --- a/tests/fuzz/fuzz-bolt12-offer-decode.c +++ b/tests/fuzz/fuzz-bolt12-offer-decode.c @@ -1,4 +1,6 @@ #include "config.h" +#include +#include #include #include #include @@ -7,12 +9,163 @@ const char *bech32_hrp = "lno"; +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->seconds_after == b->seconds_after; +} + +static bool recurrence_base_eq(const struct recurrence_base *a, + const struct recurrence_base *b) +{ + return a->basetime == b->basetime && a->proportional_amount == b->proportional_amount; +} + +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 tlv_offer_eq(const struct tlv_offer *a, const struct tlv_offer *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) + + 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_compulsory, recurrence_eq); + PTR_EQ(offer_recurrence_optional, recurrence_eq); + PTR_EQ(offer_recurrence_base, recurrence_base_eq); + PTR_EQ(offer_recurrence_paywindow, recurrence_paywindow_eq); + VAL_EQ(offer_recurrence_limit); + + return true; +} + void run(const u8 *data, size_t size) { - char *fail; + struct tlv_offer *offer, *decoded_offer; + char *fail = NULL, *encoded_offer; - offer_decode(tmpctx, (const char *)data, size, /*feature_set=*/NULL, - /*must_be_chain=*/NULL, &fail); + offer = offer_decode(tmpctx, (const char *)data, size, + /*feature_set=*/NULL, /*must_be_chain=*/NULL, &fail); + if (!offer) + goto cleanup; + encoded_offer = offer_encode(tmpctx, offer); + + decoded_offer = offer_decode(tmpctx, encoded_offer, strlen(encoded_offer), + NULL, NULL, &fail); + assert(!fail); + assert(decoded_offer); + assert(tlv_offer_eq(offer, decoded_offer)); + +cleanup: clean_tmpctx(); }