From fea4eb89686c2eccfab3f8a5c90ce9adec36cc07 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 21 Sep 2023 09:34:11 +0930 Subject: [PATCH] common: helper to generate bolt12 test vector bolt12/offers-test.json. Signed-off-by: Rusty Russell --- common/test/run-bolt12-encode-test.c | 406 +++++++++++++++++++++++++++ devtools/bolt12-cli.c | 11 +- 2 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 common/test/run-bolt12-encode-test.c diff --git a/common/test/run-bolt12-encode-test.c b/common/test/run-bolt12-encode-test.c new file mode 100644 index 000000000..23ac80a61 --- /dev/null +++ b/common/test/run-bolt12-encode-test.c @@ -0,0 +1,406 @@ +/* Pipe through jq to create offers-test.json */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_div */ +struct amount_sat amount_sat_div(struct amount_sat sat UNNEEDED, u64 div UNNEEDED) +{ fprintf(stderr, "amount_sat_div called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_mul */ +bool amount_sat_mul(struct amount_sat *res UNNEEDED, struct amount_sat sat UNNEEDED, u64 mul UNNEEDED) +{ fprintf(stderr, "amount_sat_mul called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for fromwire_amount_msat */ +struct amount_msat fromwire_amount_msat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_msat called!\n"); abort(); } +/* Generated stub for merkle_tlv */ +void merkle_tlv(const struct tlv_field *fields UNNEEDED, struct sha256 *merkle UNNEEDED) +{ fprintf(stderr, "merkle_tlv called!\n"); abort(); } +/* Generated stub for sighash_from_merkle */ +void sighash_from_merkle(const char *messagename UNNEEDED, + const char *fieldname UNNEEDED, + const struct sha256 *merkle UNNEEDED, + struct sha256 *sighash UNNEEDED) +{ fprintf(stderr, "sighash_from_merkle called!\n"); abort(); } +/* Generated stub for towire_amount_msat */ +void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED) +{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static utf8 *tal_utf8(const tal_t *ctx, const char *str) +{ + /* Non-NUL terminated! */ + return tal_dup_arr(ctx, char, str, strlen(str), 0); +} + +static void print_field(bool first, const struct tlv_field *field) +{ + printf("%s{\"type\": %"PRIu64", \"length\": %zu, \"hex\": \"%s\"}", + first ? "": ",", + field->numtype, + field->length, + tal_hexstr(tmpctx, field->value, field->length)); +} + +static void print_valid_offer(const struct tlv_offer *offer, + const char *testdesc, + const char *extradesc, + const struct tlv_field *extrafield) +{ + char *str = offer_encode(tmpctx, offer), *err; + struct tlv_offer *offer2; + + /* We only use extrafield on the end */ + if (extrafield) { + u8 *wire; + + wire = tal_arr(tmpctx, u8, 0); + towire_tlv_offer(&wire, offer); + towire_bigsize(&wire, extrafield->numtype); + towire_bigsize(&wire, extrafield->length); + towire(&wire, extrafield->value, extrafield->length); + + str = to_bech32_charset(tmpctx, "lno", wire); + } + + printf("%s{ \"description\": \"%s\", \"valid\": true, \"bolt12\": \"%s\",", + extradesc ? "," : "", + testdesc, str); + if (extradesc) + printf("\"field info\": \"%s\", ", extradesc); + + /* Print the fields. */ + printf("\"fields\": ["); + /* Re-marshal to get fields[] arr (and sanity check!) */ + offer2 = offer_decode(tmpctx, str, strlen(str), NULL, NULL, &err); + for (size_t i = 0; i < tal_count(offer2->fields); i++) + print_field(i == 0, &offer2->fields[i]); + printf("]}\n"); +} + +static void print_malformed_tlv(const char *hrp, + const char *hex, + const char *reason) +{ + u8 *data = tal_hexdata(tmpctx, hex, strlen(hex)); + assert(data); + printf(",{\"description\": \"%s\", \"valid\": false, \"bolt12\": \"%s\"}", + reason, to_bech32_charset(tmpctx, hrp, data)); +} + +static void print_invalid_offer(const struct tlv_offer *offer, const char *why) +{ + printf(",{\"description\": \"%s\", \"valid\": false, \"bolt12\": \"%s\"}", + why, offer_encode(tmpctx, offer)); +} + +int main(int argc, char *argv[]) +{ + struct tlv_offer *offer; + struct secret alice, bob, carol; + + common_setup(argv[0]); + + memset(&alice, 'A', sizeof(alice)); + memset(&bob, 'B', sizeof(bob)); + memset(&carol, 'C', sizeof(carol)); + + offer = tlv_offer_new(tmpctx); + offer->offer_node_id = tal(offer, struct pubkey); + assert(pubkey_from_secret(&alice, offer->offer_node_id)); + offer->offer_description = tal_utf8(tmpctx, "Test vectors"); + + printf("[\n"); + print_valid_offer(offer, "Minimal bolt12 offer", NULL, NULL); + + offer->offer_chains = tal_arr(offer, struct bitcoin_blkid, 1); + offer->offer_chains[0] = chainparams_for_network("testnet")->genesis_blockhash; + print_valid_offer(offer, "for testnet", + "chains[0] is testnet", NULL); + offer->offer_chains[0] = chainparams_for_network("bitcoin")->genesis_blockhash; + print_valid_offer(offer, "for bitcoin (redundant)", + "chains[0] is bitcoin", NULL); + offer->offer_chains = tal_arr(offer, struct bitcoin_blkid, 2); + offer->offer_chains[0] = chainparams_for_network("liquid")->genesis_blockhash; + offer->offer_chains[1] = chainparams_for_network("bitcoin")->genesis_blockhash; + print_valid_offer(offer, "for bitcoin or liquidv1", + "chains[0] is liquidv1, chains[1] is bitcoin", NULL); + offer->offer_chains = NULL; + + offer->offer_metadata = tal_arrz(offer, u8, 16); + print_valid_offer(offer, "with metadata", + "metadata is 16 zero bytes", NULL); + offer->offer_metadata = NULL; + + offer->offer_amount = tal(offer, u64); + *offer->offer_amount = 10000; + print_valid_offer(offer, "with amount", + "amount is 10000msat", NULL); + + offer->offer_currency = tal_utf8(offer, "USD"); + print_valid_offer(offer, "with currency", + "amount is USD $100.00", NULL); + offer->offer_currency = NULL; + offer->offer_amount = NULL; + + offer->offer_absolute_expiry = tal(offer, u64); + *offer->offer_absolute_expiry = 2051184600; + print_valid_offer(offer, "with expiry", + "expiry is 2035-01-01", NULL); + offer->offer_absolute_expiry = NULL; + + offer->offer_issuer = tal_utf8(offer, "https://bolt12.org BOLT12 industries"); + print_valid_offer(offer, "with issuer", + "issuer is 'https://bolt12.org BOLT12 industries'", NULL); + offer->offer_issuer = NULL; + + offer->offer_quantity_max = tal(offer, u64); + *offer->offer_quantity_max = 5; + print_valid_offer(offer, "with quantity", + "quantity_max is 5", NULL); + + *offer->offer_quantity_max = 0; + print_valid_offer(offer, "with unlimited (or unknown) quantity", + "quantity_max is unknown/unlimited", NULL); + + *offer->offer_quantity_max = 1; + print_valid_offer(offer, "with single quantity (weird but valid)", + "quantity_max is 1", NULL); + offer->offer_quantity_max = NULL; + + offer->offer_features = tal_arr(offer, u8, 0); + set_feature_bit(&offer->offer_features, 99); + print_valid_offer(offer, "with feature", + "feature bit 99 set", NULL); + offer->offer_features = NULL; + + offer->offer_paths = tal_arr(offer, struct blinded_path *, 1); + offer->offer_paths[0] = tal(offer->offer_paths, struct blinded_path); + assert(pubkey_from_secret(&bob, &offer->offer_paths[0]->first_node_id)); + /* Random blinding secret. */ + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[0]->blinding)); + offer->offer_paths[0]->path = tal_arr(offer->offer_paths[0], + struct onionmsg_hop *, + 2); + offer->offer_paths[0]->path[0] = tal(offer->offer_paths[0]->path, + struct onionmsg_hop); + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[0]->path[0]->blinded_node_id)); + offer->offer_paths[0]->path[0]->encrypted_recipient_data = tal_arrz(offer->offer_paths[0]->path[0], u8, 16); + offer->offer_paths[0]->path[1] = tal(offer->offer_paths[0]->path, + struct onionmsg_hop); + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[0]->path[1]->blinded_node_id)); + offer->offer_paths[0]->path[1]->encrypted_recipient_data = tal_hexdata(offer->offer_paths[0]->path[1], "1111111111111111", 16); + print_valid_offer(offer, "with blinded path via Bob (0x424242...), blinding 020202...", + "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x11*8]", NULL); + + tal_resize(&offer->offer_paths, 2); + offer->offer_paths[1] = tal(offer->offer_paths, struct blinded_path); + assert(pubkey_from_secret(&carol, &offer->offer_paths[1]->first_node_id)); + /* Random blinding secret. */ + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[1]->blinding)); + offer->offer_paths[1]->path = tal_arr(offer->offer_paths[1], + struct onionmsg_hop *, + 2); + offer->offer_paths[1]->path[0] = tal(offer->offer_paths[1]->path, + struct onionmsg_hop); + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[1]->path[0]->blinded_node_id)); + offer->offer_paths[1]->path[0]->encrypted_recipient_data = tal_arrz(offer->offer_paths[1]->path[0], u8, 16); + offer->offer_paths[1]->path[1] = tal(offer->offer_paths[1]->path, + struct onionmsg_hop); + assert(pubkey_from_hexstr("020202020202020202020202020202020202020202020202020202020202020202", 66, &offer->offer_paths[1]->path[1]->blinded_node_id)); + offer->offer_paths[1]->path[1]->encrypted_recipient_data = tal_hexdata(offer->offer_paths[1]->path[1], "2222222222222222", 16); + print_valid_offer(offer, "... and with second blinded path via Carol (0x434343...), blinding 020202...", + "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x22*8]", NULL); + offer->offer_paths = NULL; + + /* Unknown odd fields are fine */ + struct tlv_field extra; + extra.numtype = 33; + extra.length = 10; + extra.value = (u8 *)"helloworld"; + print_valid_offer(offer, "unknown odd field", + "type 33 is 'helloworld'", &extra); + + /* Now let's do the invalid ones! */ + + /* Invalid encoding forms. */ + /* offer_node_id then description */ + print_malformed_tlv("lno", + "1621020202020202020202020202020202020202020202020202020202020202020202" /* offer_node_id */ + "0A05414C494345", /* offer_description */ + "Malformed: fields out of order"); + print_malformed_tlv("lno", + "0A05414C494345" /* offer_description */ + "1621020202020202020202020202020202020202020202020202020202020202020202" /* offer_node_id */ + "48206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + "Malformed: unknown even TLV type 78"); + /* various forms of truncation */ + print_malformed_tlv("lno", "", "Malformed: empty"); + print_malformed_tlv("lno", "0A", "Malformed: truncated at type"); + print_malformed_tlv("lno", "0AFD", "Malformed: truncated in length"); + print_malformed_tlv("lno", "0A02", "Malformed: truncated after length"); + print_malformed_tlv("lno", "0A0241", "Malformed: truncated in description"); + + print_malformed_tlv("lno", + "020101" + "0A05414C494345" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: invalid offer_chains length"); + + print_malformed_tlv("lno", + "060180" + "0A05414C494345" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: truncated currency UTF-8"); + print_malformed_tlv("lno", + "06028041" + "0A05414C494345" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: invalid currency UTF-8"); + print_malformed_tlv("lno", + "0A0180" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: truncated description UTF-8"); + print_malformed_tlv("lno", + "0A028041" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: invalid description UTF-8"); + print_malformed_tlv("lno", + "0A05414C494345" + "100101" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: truncated offer_paths"); + print_malformed_tlv("lno", + "0A05414C494345" + "1002020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: zero num_hops in blinded_path"); + print_malformed_tlv("lno", + "0A05414C494345" + "100202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020102020202020202020202020202020202020202020202020202020202020202020201" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: truncated onionmsg_hop in blinded_path"); + print_malformed_tlv("lno", + "0A05414C494345" + "10030303030303030303030303030303030303030303030303030303030303030303020202020202020202020202020202020202020202020202020202020202020202010202020202020202020202020202020202020202020202020202020202020202020100" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: bad first_node_id in blinded_path"); + print_malformed_tlv("lno", + "0A05414C494345" + "10020202020202020202020202020202020202020202020202020202020202020202030303030303030303030303030303030303030303030303030303030303030303010202020202020202020202020202020202020202020202020202020202020202020100" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: bad blinding in blinded_path"); + print_malformed_tlv("lno", + "0A05414C494345" + "10020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202010303030303030303030303030303030303030303030303030303030303030303030100" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: bad blinded_node_id in onionmsg_hop"); + + print_malformed_tlv("lno", + "0A05414C494345" + "120180" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: truncated issuer UTF-8"); + print_malformed_tlv("lno", + "0A05414C494345" + "12028041" + "1621020202020202020202020202020202020202020202020202020202020202020202", + "Malformed: invalid issuer UTF-8"); + print_malformed_tlv("lno", + "0A05414C494345" + "1621020303030303030303030303030303030303030303030303030303030303030303", + "Malformed: invalid offer_node_id"); + + /* Now these are simply invalid, not bad encodings */ + /* BOLT-offers #12: + * A reader of an offer: + * - if the offer contains any TLV fields greater or equal to 80: + * - MUST NOT respond to the offer. + */ + print_malformed_tlv("lno", + "0A05414C494345" /* offer_description */ + "1621020202020202020202020202020202020202020202020202020202020202020202" /* offer_node_id */ + "50206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + "Contains type >= 80"); + + offer = tlv_offer_new(tmpctx); + offer->offer_node_id = tal(offer, struct pubkey); + assert(pubkey_from_secret(&alice, offer->offer_node_id)); + offer->offer_description = tal_utf8(tmpctx, "Test vectors"); + + offer->offer_features = tal_arr(offer, u8, 0); + set_feature_bit(&offer->offer_features, 22); + /* BOLT-offers #12: + * - if `offer_features` contains unknown _even_ bits that are non-zero: + * - MUST NOT respond to the offer. + * - SHOULD indicate the unknown bit to the user. + */ + print_invalid_offer(offer, "Contains unknown feature 22"); + offer->offer_features = NULL; + + /* BOLT-offers #12: + * - if `offer_description` is not set: + * - MUST NOT respond to the offer. + * - if `offer_node_id` is not set: + * - MUST NOT respond to the offer. + */ + offer->offer_description = NULL; + print_invalid_offer(offer, "Missing offer_description"); + offer->offer_description = tal_utf8(tmpctx, "Test vectors"); + + offer->offer_node_id = NULL; + print_invalid_offer(offer, "Missing offer_node_id"); + offer->offer_node_id = tal(offer, struct pubkey); + assert(pubkey_from_secret(&alice, offer->offer_node_id)); + + printf("]\n"); + common_shutdown(); +} diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index 21222b6a5..0a33e229d 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -1,5 +1,6 @@ #include "config.h" #include +#include #include #include #include @@ -421,7 +422,7 @@ static u64 get_offer_type(const char *name) struct name_map { const char *name; u64 val; - } map = { + } map[] = { /* BOLT-offers #12: * 1. `tlv_stream`: `offer` * 2. types: @@ -652,9 +653,9 @@ static u64 get_offer_type(const char *name) static u8 *get_tlv_val(const tal_t *ctx, const char *val) { - u8 *val = tal_hexdata(ctx, val, strlen(val)); - if (val) - return val; + u8 *data = tal_hexdata(ctx, val, strlen(val)); + if (data) + return data; /* Literal string */ return tal_dup_arr(ctx, u8, (u8 *)val, strlen(val), 0); } @@ -673,7 +674,7 @@ int main(int argc, char *argv[]) opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); opt_register_noarg("--help|-h", opt_usage_and_exit, "decode|decodehex \n" - "encodehex ...", + "encodehex ...\n" "encode [ ]...", "Show this message"); opt_register_version();