diff --git a/common/test/Makefile b/common/test/Makefile index 5f644f178..46b579a41 100644 --- a/common/test/Makefile +++ b/common/test/Makefile @@ -82,6 +82,18 @@ common/test/run-bolt12_merkle: \ wire/peer_wiregen.o \ wire/towire.o +common/test/run-bolt12-format-string-test: \ + common/amount.o \ + common/bigsize.o \ + common/base32.o \ + common/bech32.o \ + common/bech32_util.o \ + common/bolt12.o \ + common/node_id.o \ + wire/bolt12$(EXP)_wiregen.o \ + wire/tlvstream.o \ + wire/towire.o + common/test/run-bolt12_merkle-json: \ common/base32.o \ common/wireaddr.o diff --git a/common/test/run-bolt12-format-string-test.c b/common/test/run-bolt12-format-string-test.c new file mode 100644 index 000000000..2a6ed854d --- /dev/null +++ b/common/test/run-bolt12-format-string-test.c @@ -0,0 +1,210 @@ +/* Pipe through jq to create format-string-test.json */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for features_unsupported */ +int features_unsupported(const struct feature_set *our_features UNNEEDED, + const u8 *their_features UNNEEDED, + enum feature_place p UNNEEDED) +{ fprintf(stderr, "features_unsupported called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_blinded_path */ +struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) +{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_pad */ +void fromwire_pad(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_pad called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_tu32 */ +u32 fromwire_tu32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_tu32 called!\n"); abort(); } +/* Generated stub for fromwire_tu64 */ +u64 fromwire_tu64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_tu64 called!\n"); abort(); } +/* Generated stub for fromwire_u16 */ +u16 fromwire_u16(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u16 called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for fromwire_utf8_array */ +void fromwire_utf8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, char *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_utf8_array 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_blinded_path */ +void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) +{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static void example2(const char *comment, bool valid, + const char *str, bool final) +{ + printf("{\n"); + printf("\"comment\": \"%s\",\n", comment); + printf("\"valid\": %s,\n", valid ? "true": "false"); + printf("\"string\": \"%s\"\n", str); + printf("}%s\n", final ? "" : ","); +} + +static void example(const char *comment, bool valid, const char *str) +{ + example2(comment, valid, str, false); +} + +static void example_final(const char *comment, bool valid, const char *str) +{ + example2(comment, valid, str, true); +} + +static utf8 *tal_utf8(const tal_t *ctx, const char *str) +{ + /* Non-NUL terminated! */ + return tal_dup_arr(ctx, char, str, strlen(str), 0); +} + +int main(int argc, char *argv[]) +{ + struct tlv_offer *offer; + struct secret alice; + char *str; + + common_setup(argv[0]); + + offer = tlv_offer_new(tmpctx); + /* BOLT-offers #12: + * A writer of an offer: + * - MUST NOT set any tlv fields greater or equal to 80, or tlv field 0. + * - MUST set `offer_node_id` to the node's public key to request the invoice from. + * - MUST set `offer_description` to a complete description of the purpose + * of the payment. + * - if the chain for the invoice is not solely bitcoin: + * - MUST specify `offer_chains` the offer is valid for. + * - otherwise: + * - MAY omit `offer_chains`, implying that bitcoin is only chain. + * - if a specific minimum `offer_amount` is required for successful payment: + * - MUST set `offer_amount` to the amount expected (per item). + * - if the currency for `offer_amount` is that of all entries in `chains`: + * - MUST specify `amount` in multiples of the minimum lightning-payable unit + * (e.g. milli-satoshis for bitcoin). + * - otherwise: + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 + * exponent (e.g. USD cents). + * - otherwise: + * - MUST NOT set `offer_amount` + * - MUST NOT set `offer_currency` + * - MAY set `offer_metadata` for its own use. + * - if it supports bolt12 offer features: + * - MUST set `offer_features`.`features` to the bitmap of bolt12 features. + * - if the offer expires: + * - MUST set `offer_absolute_expiry` `seconds_from_epoch` to the number of seconds + * after midnight 1 January 1970, UTC that invoice_request should not be + * attempted. + * - if it is connected only by private channels: + * - MUST include `offer_paths` containing one or more paths to the node from + * publicly reachable nodes. + * - otherwise: + * - MAY include `offer_paths`. + * - if it includes `offer_paths`: + * - SHOULD ignore any invoice_request which does not use the path. + * - if it sets `offer_issuer`: + * - SHOULD set it to identify the issuer of the invoice clearly. + * - if it includes a domain name: + * - SHOULD begin it with either user@domain or domain + * - MAY follow with a space and more text + * - if it can supply more than one item for a single invoice: + * - if the maximum quantity is known: + * - MUST set that maximum in `offer_quantity_max`. + * - MUST NOT set `offer_quantity_max` to 0. + * - otherwise: + * - MUST set `offer_quantity_max` to 0. + * - otherwise: + * - MUST NOT set `offer_quantity_max`. + */ + memset(&alice, 'A', sizeof(alice)); + offer->offer_node_id = tal(offer, struct pubkey); + assert(pubkey_from_secret(&alice, offer->offer_node_id)); + offer->offer_description = tal_utf8(offer, "An example description"); + offer->offer_issuer = tal_utf8(offer, "BOLT 12 industries"); + offer->offer_amount = tal(offer, u64); + /* 1M msat */ + *offer->offer_amount = 1000000; + + str = offer_encode(tmpctx, offer); + printf("[\n"); + + example("A complete string is valid", true, str); + example("+ can join anywhere", true, + tal_fmt(tmpctx, "%.*s+%s", 1, str, str + 1)); + example("Multiple + can join", true, + tal_fmt(tmpctx, "%.*s+%.*s+%.*s+%.*s+%s", + 15, str, + 31, str + 15, + 18, str + 15 + 31, + 70, str + 15 + 31 + 18, + str + 15 + 31 + 18 + 70)); + example("+ can be followed by whitespace", true, + tal_fmt(tmpctx, "%.*s+ %.*s+ %.*s+\\n%.*s+\\r\\n %s", + 15, str, + 31, str + 15, + 18, str + 15 + 31, + 70, str + 15 + 31 + 18, + str + 15 + 31 + 18 + 70)); + + example("+ must be surrounded by bech32 characters", false, + tal_fmt(tmpctx, "%s+", str)); + example("+ must be surrounded by bech32 characters", false, + tal_fmt(tmpctx, "%s+ ", str)); + example("+ must be surrounded by bech32 characters", false, + tal_fmt(tmpctx, "+%s", str)); + example("+ must be surrounded by bech32 characters", false, + tal_fmt(tmpctx, "+ %s", str)); + example_final("+ must be surrounded by bech32 characters", false, + tal_fmt(tmpctx, "%.*s++%s", 2, str, str+2)); + printf("]\n"); + common_shutdown(); +} diff --git a/common/test/run-bolt12_decode.c b/common/test/run-bolt12_decode.c index 6ddf6656d..5bbb6f7a1 100644 --- a/common/test/run-bolt12_decode.c +++ b/common/test/run-bolt12_decode.c @@ -203,6 +203,7 @@ int main(int argc, char *argv[]) actual = (string_to_data(tmpctx, str, strlen(str), "lno", &dlen, &fail) != NULL); assert(actual == valid); + printf("%s %s\n", str, valid ? "OK": "INVALID"); } out: common_shutdown();