diff --git a/common/blindedpath.c b/common/blindedpath.c index f081e0d7c..ae6ef2960 100644 --- a/common/blindedpath.c +++ b/common/blindedpath.c @@ -7,7 +7,11 @@ #include #include -/* FIXME: Pad with dummy nodes, but make sure to check them on recv! */ +#ifndef SUPERVERBOSE +#define SUPERVERBOSE(...) +#endif + +/* Obsolete version: use enctlv() helper. */ struct onionmsg_path **make_blindedpath(const tal_t *ctx, const struct pubkey *route, struct pubkey *initial_blinding, @@ -95,3 +99,145 @@ struct onionmsg_path **make_blindedpath(const tal_t *ctx, return path; } + +/* Blinds node_id and calculates next blinding factor. */ +static bool blind_node(const struct privkey *blinding, + const struct secret *ss, + const struct pubkey *node, + struct pubkey *node_alias, + struct privkey *next_blinding) +{ + struct secret node_id_blinding; + struct pubkey blinding_pubkey; + struct sha256 h; + + /* + * Blinded node_id for N(i), private key known only by N(i): + * B(i) = HMAC256("blinded_node_id", ss(i)) * P(i) + */ + subkey_from_hmac("blinded_node_id", ss, &node_id_blinding); + SUPERVERBOSE("\t\"HMAC256('blinded_node_id', ss)\": \"%s\",\n", + type_to_string(tmpctx, struct secret, + &node_id_blinding)); + + *node_alias = *node; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &node_alias->pubkey, + node_id_blinding.data) != 1) + return false; + SUPERVERBOSE("\t\"blinded_node_id\": \"%s\",\n", + type_to_string(tmpctx, struct pubkey, node_alias)); + + /* + * Ephemeral private key, only known by N(r): + * e(i+1) = H(E(i) || ss(i)) * e(i) + */ + if (!pubkey_from_privkey(blinding, &blinding_pubkey)) + return false; + SUPERVERBOSE("\t\"E\": \"%s\",\n", + type_to_string(tmpctx, struct pubkey, &blinding_pubkey)); + + blinding_hash_e_and_ss(&blinding_pubkey, ss, &h); + SUPERVERBOSE("\t\"H(E || ss)\": \"%s\",\n", + type_to_string(tmpctx, struct sha256, &h)); + blinding_next_privkey(blinding, &h, next_blinding); + SUPERVERBOSE("\t\"next_e\": \"%s\",\n", + type_to_string(tmpctx, struct privkey, next_blinding)); + + return true; +} + +static u8 *enctlv_from_encmsg(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct tlv_encmsg_tlvs *encmsg, + struct privkey *next_blinding, + struct pubkey *node_alias) +{ + /* https://github.com/lightningnetwork/lightning-rfc/blob/route-blinding/proposals/route-blinding.md */ + struct secret ss, rho; + u8 *ret; + int ok; + /* All-zero npub */ + static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; + + /* + * shared secret known only by N(r) and N(i): + * ss(i) = H(e(i) * P(i)) = H(k(i) * E(i)) + */ + if (secp256k1_ecdh(secp256k1_ctx, ss.data, + &node->pubkey, blinding->secret.data, + NULL, NULL) != 1) + return NULL; + SUPERVERBOSE("\t\"ss\": \"%s\",\n", + type_to_string(tmpctx, struct secret, &ss)); + + /* This calculates the node's alias, and next blinding */ + if (!blind_node(blinding, &ss, node, node_alias, next_blinding)) + return NULL; + + /* Marshall */ + ret = tal_arr(ctx, u8, 0); + towire_encmsg_tlvs(&ret, encmsg); + SUPERVERBOSE("\t\"encmsg_hex\": \"%s\",\n", tal_hex(tmpctx, ret)); + + /* + * Key used to encrypt payload for N(i) by N(r): + * rho(i) = HMAC256("rho", ss(i)) + */ + subkey_from_hmac("rho", &ss, &rho); + SUPERVERBOSE("\t\"rho\": \"%s\",\n", + type_to_string(tmpctx, struct secret, &rho)); + + /* Encrypt in place */ + towire_pad(&ret, crypto_aead_chacha20poly1305_ietf_ABYTES); + + ok = crypto_aead_chacha20poly1305_ietf_encrypt(ret, NULL, + ret, + tal_bytelen(ret) + - crypto_aead_chacha20poly1305_ietf_ABYTES, + NULL, 0, + NULL, npub, + rho.data); + assert(ok == 0); + + return ret; +} + +u8 *create_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct pubkey *next_node, + size_t padlen, + const struct pubkey *override_blinding, + struct privkey *next_blinding, + struct pubkey *node_alias) +{ + struct tlv_encmsg_tlvs *encmsg = tlv_encmsg_tlvs_new(tmpctx); + if (padlen) + encmsg->padding = tal_arrz(encmsg, u8, padlen); + encmsg->next_node_id = cast_const(struct pubkey *, next_node); + encmsg->next_blinding = cast_const(struct pubkey *, override_blinding); + + return enctlv_from_encmsg(ctx, blinding, node, encmsg, + next_blinding, node_alias); +} + +u8 *create_final_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *final_node, + size_t padlen, + const struct secret *self_id, + struct pubkey *node_alias) +{ + struct tlv_encmsg_tlvs *encmsg = tlv_encmsg_tlvs_new(tmpctx); + struct privkey unused_next_blinding; + + if (padlen) + encmsg->padding = tal_arrz(encmsg, u8, padlen); + if (self_id) + encmsg->self_id = (u8 *)tal_dup(encmsg, struct secret, self_id); + + return enctlv_from_encmsg(ctx, blinding, final_node, encmsg, + &unused_next_blinding, node_alias); +} diff --git a/common/blindedpath.h b/common/blindedpath.h index bf52d9a8a..6c2124450 100644 --- a/common/blindedpath.h +++ b/common/blindedpath.h @@ -1,10 +1,14 @@ #ifndef LIGHTNING_COMMON_BLINDEDPATH_H #define LIGHTNING_COMMON_BLINDEDPATH_H #include "config.h" +#include +#include #include struct route_info; struct pubkey; +struct privkey; +struct secret; /* Fills in *initial_blinding and *final_blinding and returns * onionmsg_path array for this route */ @@ -12,4 +16,47 @@ struct onionmsg_path **make_blindedpath(const tal_t *ctx, const struct pubkey *route, struct pubkey *initial_blinding, struct pubkey *final_blinding); + +/** + * create_enctlv - Encrypt an encmsg to form an enctlv. + * @ctx: tal context + * @blinding: e(i), the blinding secret + * @node: the pubkey of the node to encrypt for + * @next_node: the pubkey of the next node, to place in enctlv + * @padlen: if non-zero, the bytes of padding to add (also adds 2 byte padding hdr) + * @override_blinding: the optional blinding point to place in enctlv + * @next_blinding: (out) e(i+1), the next blinding secret. + * @node_alias: (out) the blinded pubkey of the node to tell the recipient. + * + * Returns the enctlv blob, or NULL if the secret is invalid. + */ +u8 *create_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct pubkey *next_node, + size_t padlen, + const struct pubkey *override_blinding, + struct privkey *next_blinding, + struct pubkey *node_alias) + NON_NULL_ARGS(2, 3, 4, 7, 8); + +/** + * create_final_enctlv - Encrypt an encmsg to form the final enctlv. + * @ctx: tal context + * @blinding: e(i), the blinding secret + * @final_node: the pubkey of the node to encrypt for + * @padlen: if non-zero, the bytes of padding to add (also adds 2 byte padding hdr) + * @self_id: secret to include in enctlv, if not NULL. + * @node_alias: (out) the blinded pubkey of the node to tell the recipient. + * + * If it fails, it means one of the privkeys is bad. + */ +u8 *create_final_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *final_node, + size_t padlen, + const struct secret *self_id, + struct pubkey *node_alias) + NON_NULL_ARGS(2, 3, 6); + #endif /* LIGHTNING_COMMON_BLINDEDPATH_H */ diff --git a/common/test/Makefile b/common/test/Makefile index 18f78dc8a..e6644f374 100644 --- a/common/test/Makefile +++ b/common/test/Makefile @@ -20,6 +20,7 @@ ALL_TEST_PROGRAMS += $(COMMON_TEST_PROGRAMS) # Sphinx test wants to decode TLVs. common/test/run-sphinx: wire/onion$(EXP)_wiregen.o wire/towire.o wire/fromwire.o +common/test/run-blindedpath_enctlv: wire/onion$(EXP)_wiregen.o wire/towire.o wire/fromwire.o wire/tlvstream.o common/test/run-param \ common/test/run-json: \ diff --git a/common/test/run-blindedpath_enctlv.c b/common/test/run-blindedpath_enctlv.c new file mode 100644 index 000000000..6b76b70e4 --- /dev/null +++ b/common/test/run-blindedpath_enctlv.c @@ -0,0 +1,161 @@ +#include "../bigsize.c" +#include "../blinding.c" +#include "../hmac.c" +#include "../type_to_string.c" +#include +#include + +#undef SUPERVERBOSE +#define SUPERVERBOSE printf + #include "../blindedpath.c" + +/* 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_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_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 fromwire_amount_sat */ +struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_sat 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(); } +/* Generated stub for towire_amount_sat */ +void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static void json_strfield(const char *name, const char *val) +{ + printf("\t\"%s\": \"%s\",\n", name, val); +} + +int main(int argc, char *argv[]) +{ + struct privkey alice, bob, carol, dave, blinding, override_blinding; + struct pubkey alice_id, bob_id, carol_id, dave_id, blinding_pub, override_blinding_pub, alias; + u8 *enctlv; + + common_setup(argv[0]); + + memset(&alice, 'A', sizeof(alice)); + memset(&bob, 'B', sizeof(bob)); + memset(&carol, 'C', sizeof(carol)); + memset(&dave, 'D', sizeof(dave)); + pubkey_from_privkey(&alice, &alice_id); + pubkey_from_privkey(&bob, &bob_id); + pubkey_from_privkey(&carol, &carol_id); + pubkey_from_privkey(&dave, &dave_id); + + memset(&blinding, 5, sizeof(blinding)); + pubkey_from_privkey(&blinding, &blinding_pub); + + /* We output the JSON test vectors. */ + printf("[{"); + json_strfield("test name", "Simple enctlv for Alice, next is Bob"); + json_strfield("node_privkey", + type_to_string(tmpctx, struct privkey, &alice)); + json_strfield("node_id", + type_to_string(tmpctx, struct pubkey, &alice_id)); + json_strfield("blinding_secret", + type_to_string(tmpctx, struct privkey, &blinding)); + json_strfield("blinding", + type_to_string(tmpctx, struct pubkey, &blinding_pub)); + printf("\t\"encmsg\": {\n" + "\t\t\"next_node_id\": \"%s\"\n" + "\t},\n", + type_to_string(tmpctx, struct pubkey, &bob_id)); + + enctlv = create_enctlv(tmpctx, &blinding, &alice_id, &bob_id, + 0, NULL, &blinding, &alias); + printf("\t\"enctlv_hex\": \"%s\"\n" + "},\n", + tal_hex(tmpctx, enctlv)); + + pubkey_from_privkey(&blinding, &blinding_pub); + memset(&override_blinding, 7, sizeof(override_blinding)); + pubkey_from_privkey(&override_blinding, &override_blinding_pub); + + printf("{"); + json_strfield("test name", + "Blinding-key-override enctlv for Bob, next is Carol"); + json_strfield("node_privkey", + type_to_string(tmpctx, struct privkey, &bob)); + json_strfield("node_id", + type_to_string(tmpctx, struct pubkey, &bob_id)); + json_strfield("blinding_secret", + type_to_string(tmpctx, struct privkey, &blinding)); + json_strfield("blinding", + type_to_string(tmpctx, struct pubkey, &blinding_pub)); + printf("\t\"encmsg\": {\n" + "\t\t\"next_node_id\": \"%s\",\n" + "\t\t\"blinding\": \"%s\"\n" + "\t},\n", + type_to_string(tmpctx, struct pubkey, &carol_id), + type_to_string(tmpctx, struct privkey, &override_blinding)); + + enctlv = create_enctlv(tmpctx, &blinding, &bob_id, &carol_id, + 0, &override_blinding_pub, &blinding, &alias); + printf("\t\"enctlv_hex\": \"%s\"\n" + "},\n", + tal_hex(tmpctx, enctlv)); + + /* That replaced the blinding */ + blinding = override_blinding; + blinding_pub = override_blinding_pub; + + printf("{"); + json_strfield("test name", "Padded enctlv for Carol, next is Dave"); + json_strfield("node_privkey", + type_to_string(tmpctx, struct privkey, &carol)); + json_strfield("node_id", + type_to_string(tmpctx, struct pubkey, &carol_id)); + json_strfield("blinding_secret", + type_to_string(tmpctx, struct privkey, &blinding)); + json_strfield("blinding", + type_to_string(tmpctx, struct pubkey, &blinding_pub)); + printf("\t\"encmsg\": {\n" + "\t\t\"next_node_id\": \"%s\",\n" + "\t\t\"padding\": \"%s\"\n" + "\t},\n", + type_to_string(tmpctx, struct pubkey, &dave_id), + tal_hex(tmpctx, tal_arrz(tmpctx, u8, 35))); + + enctlv = create_enctlv(tmpctx, &blinding, &carol_id, &dave_id, + 35, NULL, &blinding, &alias); + printf("\t\"enctlv_hex\": \"%s\"\n" + "}]\n", + tal_hex(tmpctx, enctlv)); + + common_shutdown(); +}