diff --git a/common/bolt11.c b/common/bolt11.c index f21279c50..0c9adbeb3 100644 --- a/common/bolt11.c +++ b/common/bolt11.c @@ -302,6 +302,37 @@ static char *decode_n(struct bolt11 *b11, return NULL; } +/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #11: + * + * * `s` (16): `data_length` 52. This 256-bit secret prevents + * forwarding nodes from probing the payment recipient. + */ +static char *decode_s(struct bolt11 *b11, + struct hash_u5 *hu5, + u5 **data, size_t *data_len, + size_t data_length, + bool *have_s) +{ + if (*have_s) + return unknown_field(b11, hu5, data, data_len, 's', + data_length); + + /* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #11: + * + * A reader... MUST skip over unknown fields, OR an `f` field + * with unknown `version`, OR `p`, `h`, `s` or `n` fields that do + * NOT have `data_length`s of 52, 52, 52 or 53, respectively. */ + if (data_length != 52) + return unknown_field(b11, hu5, data, data_len, 's', + data_length); + + b11->payment_secret = tal(b11, struct secret); + pull_bits_certain(hu5, data, data_len, b11->payment_secret, 256, + false); + *have_s = true; + return NULL; +} + /* BOLT #11: * * `f` (9): `data_length` variable, depending on version. Fallback @@ -500,6 +531,7 @@ struct bolt11 *new_bolt11(const tal_t *ctx, b11->expiry = DEFAULT_X; b11->features = tal_arr(b11, u8, 0); b11->min_final_cltv_expiry = DEFAULT_C; + b11->payment_secret = NULL; if (msat) b11->msat = tal_dup(b11, struct amount_msat, msat); @@ -519,7 +551,7 @@ struct bolt11 *bolt11_decode(const tal_t *ctx, const char *str, struct hash_u5 hu5; struct sha256 hash; bool have_p = false, have_n = false, have_d = false, have_h = false, - have_x = false, have_c = false; + have_x = false, have_c = false, have_s = false; b11->routes = tal_arr(b11, struct route_info *, 0); @@ -694,6 +726,10 @@ struct bolt11 *bolt11_decode(const tal_t *ctx, const char *str, problem = decode_9(b11, &hu5, &data, &data_len, data_length); break; + case 's': + problem = decode_s(b11, &hu5, &data, &data_len, + data_length, &have_s); + break; default: unknown_field(b11, &hu5, &data, &data_len, bech32_charset[type], data_length); @@ -874,6 +910,11 @@ static void encode_c(u5 **data, u16 min_final_cltv_expiry) push_varlen_field(data, 'c', min_final_cltv_expiry); } +static void encode_s(u5 **data, const struct secret *payment_secret) +{ + push_field(data, 's', payment_secret, 256); +} + static void encode_f(u5 **data, const u8 *fallback) { struct bitcoin_address pkh; @@ -1040,6 +1081,9 @@ char *bolt11_encode_(const tal_t *ctx, if (b11->min_final_cltv_expiry != DEFAULT_C) encode_c(&data, b11->min_final_cltv_expiry); + if (b11->payment_secret) + encode_s(&data, b11->payment_secret); + for (size_t i = 0; i < tal_count(b11->fallbacks); i++) encode_f(&data, b11->fallbacks[i]); diff --git a/common/bolt11.h b/common/bolt11.h index 5096fcd05..2b337ff28 100644 --- a/common/bolt11.h +++ b/common/bolt11.h @@ -64,6 +64,9 @@ struct bolt11 { /* signature of sha256 of entire thing. */ secp256k1_ecdsa_signature sig; + /* payment secret, if any. */ + struct secret *payment_secret; + /* Features bitmap, if any. */ u8 *features; diff --git a/common/test/run-bolt11.c b/common/test/run-bolt11.c index 0597c3d96..6e7158327 100644 --- a/common/test/run-bolt11.c +++ b/common/test/run-bolt11.c @@ -100,6 +100,12 @@ static void test_b11(const char *b11str, else assert(streq(b11->description, expect_b11->description)); + if (!b11->payment_secret) + assert(!expect_b11->payment_secret); + else + assert(memeq(b11->payment_secret, sizeof(*b11->payment_secret), + expect_b11->payment_secret, + sizeof(*expect_b11->payment_secret))); assert(memeq(b11->features, tal_bytelen(b11->features), expect_b11->features, tal_bytelen(expect_b11->features))); assert(b11->expiry == expect_b11->expiry); @@ -312,10 +318,10 @@ int main(void) test_b11("lntb30m1pw2f2yspp5s59w4a0kjecw3zyexm7zur8l8n4scw674w8sftjhwec33km882gsdpa2pshjmt9de6zqun9w96k2um5ypmkjargypkh2mr5d9cxzun5ypeh2ursdae8gxqruyqvzddp68gup69uhnzwfj9cejuvf3xshrwde68qcrswf0d46kcarfwpshyaplw3skw0tdw4k8g6tsv9e8g4a3hx0v945csrmpm7yxyaamgt2xu7mu4xyt3vp7045n4k4czxf9kj0vw0m8dr5t3pjxuek04rtgyy8uzss5eet5gcyekd6m7u0mzv5sp7mdsag", b11, NULL); - /* BOLT-a76d61dc9893eec75b2e9c4a361354c356c46894 #11: + /* BOLT-d8d45ed403e54cffdb049d2e44d1100e41df013c #11: * - * > ### Please send $30 for coffee beans to the same peer, which supports features 1 and 9 - * > lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzsze992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq73t7cl + * > ### Please send $30 for coffee beans to the same peer, which supports features 15 and 99, using secret 0x1111111111111111111111111111111111111111111111111111111111111111 + * > lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqqq4u9s93jtgysm3mrwll70zr697y3mf902hvxwej0v7c62rsltw83ng0pu8w3j230sluc5gxkdmm9dvpy9y6ggtjd2w544mzdrcs42t7sqdkcy8h * * Breakdown: * @@ -327,11 +333,14 @@ int main(void) * * `d`: short description * * `q5`: `data_length` (`q` = 0, `5` = 20; 0 * 32 + 20 == 20) * * `vdhkven9v5sxyetpdees`: 'coffee beans' + * * `s`: payment secret + * * `p5`: `data_length` (`p` = 1, `5` = 20; 1 * 32 + 20 == 52) + * * `zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs`: 0x1111111111111111111111111111111111111111111111111111111111111111 * * `9`: features - * * `qz`: `data_length` (`q` = 0, `z` = 2; 0 * 32 + 2 == 2) - * * `sz`: b1000000010 - * * `e992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq`: signature - * * `73t7cl`: Bech32 checksum + * * `q5`: `data_length` (`q` = 0, `5` = 20; 0 * 32 + 20 == 20) 4 + * * `sqqqqqqqqqqqqqqqpqqq`: b1000....00001000000000000000 + * * `pqqq4u9s93jtgysm3mrwll70zr697y3mf902hvxwej0v7c62rsltw83ng0pu8w3j230sluc5gxkdmm9dvpy9y6ggtjd2w544mzdrcs42t7sq`: signature + * * `dkcy8h`: Bech32 checksum */ msatoshi = AMOUNT_MSAT(25 * (1000ULL * 100000000) / 1000); b11 = new_bolt11(tmpctx, &msatoshi); @@ -343,20 +352,22 @@ int main(void) abort(); b11->receiver_id = node; b11->description = "coffee beans"; - set_feature_bit(&b11->features, 1); - set_feature_bit(&b11->features, 9); + b11->payment_secret = tal(b11, struct secret); + memset(b11->payment_secret, 0x11, sizeof(*b11->payment_secret)); + set_feature_bit(&b11->features, 15); + set_feature_bit(&b11->features, 99); - test_b11("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzsze992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq73t7cl", b11, NULL); + test_b11("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqqq4u9s93jtgysm3mrwll70zr697y3mf902hvxwej0v7c62rsltw83ng0pu8w3j230sluc5gxkdmm9dvpy9y6ggtjd2w544mzdrcs42t7sqdkcy8h", b11, NULL); - /* BOLT-a76d61dc9893eec75b2e9c4a361354c356c46894 #11: + /* BOLT-d8d45ed403e54cffdb049d2e44d1100e41df013c #11: * - * > # Same, but using invalid unknown feature 100 - * > lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7 + * > # Same, but adding invalid unknown feature 100 + * > lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqqqqu7fz6pjqczdm3jp3qps7xntj2w2mm70e0ckhw3c5xk9p36pvk3sewn7ncaex6uzfq0vtqzy28se6pcwn790vxex7xystzumhg55p6qq9wq7td */ /* This one can be encoded, but not decoded */ set_feature_bit(&b11->features, 100); badstr = bolt11_encode(tmpctx, b11, false, test_sign, NULL); - assert(streq(badstr, "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7")); + assert(streq(badstr, "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqqqqu7fz6pjqczdm3jp3qps7xntj2w2mm70e0ckhw3c5xk9p36pvk3sewn7ncaex6uzfq0vtqzy28se6pcwn790vxex7xystzumhg55p6qq9wq7td")); assert(!bolt11_decode(tmpctx, badstr, NULL, &fail)); assert(streq(fail, "9: unknown feature bit 100")); diff --git a/devtools/bolt11-cli.c b/devtools/bolt11-cli.c index 19e77d158..0d64dc0c1 100644 --- a/devtools/bolt11-cli.c +++ b/devtools/bolt11-cli.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,10 @@ int main(int argc, char *argv[]) printf("description_hash: %s\n", tal_hexstr(ctx, b11->description_hash, sizeof(*b11->description_hash))); + if (b11->payment_secret) + printf("payment_secret: %s\n", + tal_hexstr(ctx, b11->payment_secret, + sizeof(*b11->payment_secret))); if (tal_bytelen(b11->features)) { printf("features:"); for (size_t i = 0; i < tal_bytelen(b11->features) * CHAR_BIT; i++) { diff --git a/lightningd/invoice.c b/lightningd/invoice.c index cf1bfb4c8..1b9c449f4 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -1084,6 +1084,9 @@ static struct command_result *json_decodepay(struct command *cmd, b11->description_hash); json_add_num(response, "min_final_cltv_expiry", b11->min_final_cltv_expiry); + if (b11->payment_secret) + json_add_secret(response, "payment_secret", + b11->payment_secret); if (b11->features) json_add_hex_talarr(response, "features", b11->features); if (tal_count(b11->fallbacks)) { diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index bf8fe52dc..4524061a7 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -188,6 +188,11 @@ void json_add_node_id(struct json_stream *response UNNEEDED, void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, unsigned int value UNNEEDED) { fprintf(stderr, "json_add_num called!\n"); abort(); } +/* Generated stub for json_add_secret */ +void json_add_secret(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + const struct secret *secret UNNEEDED) +{ fprintf(stderr, "json_add_secret called!\n"); abort(); } /* Generated stub for json_add_sha256 */ void json_add_sha256(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, const struct sha256 *hash UNNEEDED)