diff --git a/devtools/Makefile b/devtools/Makefile index 0ca66b4f1..a272feea5 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -1,6 +1,10 @@ DEVTOOLS_SRC := devtools/gen_print_wire.c devtools/gen_print_onion_wire.c devtools/print_wire.c DEVTOOLS_OBJS := $(DEVTOOLS_SRC:.c=.o) DEVTOOLS := devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore devtools/gossipwith devtools/create-gossipstore devtools/mkcommit devtools/mkfunding devtools/mkclose devtools/mkgossip devtools/mkencoded devtools/checkchannels devtools/mkquery devtools/lightning-checkmessage +ifeq ($(EXPERIMENTAL_FEATURES),1) +DEVTOOLS += devtools/blindedpath +endif + DEVTOOLS_TOOL_SRC := $(DEVTOOLS:=.c) DEVTOOLS_TOOL_OBJS := $(DEVTOOLS_TOOL_SRC:.c=.o) @@ -66,6 +70,8 @@ devtools/onion.c: ccan/config.h devtools/onion: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/onion.o common/sphinx.o +devtools/blindedpath: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/blindedpath.o common/sphinx.o + devtools/gossipwith: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/gen_peer_wire.o devtools/gossipwith.o common/cryptomsg.o common/cryptomsg.o common/crypto_sync.o $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS): wire/wire.h devtools/gen_print_wire.h devtools/gen_print_onion_wire.h diff --git a/devtools/blindedpath.c b/devtools/blindedpath.c new file mode 100644 index 000000000..d21490b67 --- /dev/null +++ b/devtools/blindedpath.c @@ -0,0 +1,316 @@ +#include "config.h" +#include <assert.h> +#include <bitcoin/privkey.h> +#include <ccan/err/err.h> +#include <ccan/mem/mem.h> +#include <ccan/opt/opt.h> +#include <ccan/str/hex/hex.h> +#include <ccan/tal/tal.h> +#include <common/hmac.h> +#include <common/sphinx.h> +#include <common/type_to_string.h> +#include <common/utils.h> +#include <common/version.h> +#include <secp256k1.h> +#include <secp256k1_ecdh.h> +#include <sodium/crypto_auth_hmacsha256.h> +#include <sodium/crypto_aead_chacha20poly1305.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* Tal wrappers for opt. */ +static void *opt_allocfn(size_t size) +{ + return tal_arr_label(NULL, char, size, TAL_LABEL("opt_allocfn", "")); +} + +static void *tal_reallocfn(void *ptr, size_t size) +{ + if (!ptr) + return opt_allocfn(size); + tal_resize_(&ptr, 1, size, false); + return ptr; +} + +static void tal_freefn(void *ptr) +{ + tal_free(ptr); +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct sha256 hash_e_and_ss(const struct pubkey *e, + const struct secret *ss) +{ + u8 der[PUBKEY_CMPR_LEN]; + struct sha256_ctx shactx; + struct sha256 h; + + pubkey_to_der(der, e); + sha256_init(&shactx); + sha256_update(&shactx, der, sizeof(der)); + sha256_update(&shactx, ss->data, sizeof(ss->data)); + sha256_done(&shactx, &h); + + return h; +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct pubkey next_pubkey(const struct pubkey *pk, + const struct sha256 *h) +{ + struct pubkey ret; + + ret = *pk; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &ret.pubkey, h->u.u8) + != 1) + abort(); + + return ret; +} + +/* e(i+1) = H(E(i) || ss(i)) * e(i) */ +static struct privkey next_privkey(const struct privkey *e, + const struct sha256 *h) +{ + struct privkey ret; + + ret = *e; + if (secp256k1_ec_privkey_tweak_mul(secp256k1_ctx, ret.secret.data, + h->u.u8) != 1) + abort(); + + return ret; +} + +int main(int argc, char **argv) +{ + bool first = false; + + setup_locale(); + + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | + SECP256K1_CONTEXT_SIGN); + + opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); + opt_register_noarg("--help|-h", opt_usage_and_exit, + "\n\n\tcreate <nodeid>[/<scid>]...\n" + "\tunwrap <privkey> <onion> <blinding>\n", + "Show this message"); + opt_register_noarg("--first-node", opt_set_bool, &first, + "Don't try to tweak key to unwrap onion"); + opt_register_version(); + + opt_parse(&argc, argv, opt_log_stderr_exit); + setup_tmpctx(); + + if (argc < 2) + errx(1, "You must specify create or unwrap"); + if (streq(argv[1], "create")) { + struct privkey e; + struct pubkey *pk_e, *b, *nodes; + struct secret *rho; + size_t num = argc - 2; + + if (argc < 3) + errx(1, "create requires at least one nodeid"); + + /* P(i) */ + nodes = tal_arr(tmpctx, struct pubkey, num); + /* E(i) */ + pk_e = tal_arr(tmpctx, struct pubkey, num); + /* B(i) */ + b = tal_arr(tmpctx, struct pubkey, num); + /* rho(i) */ + rho = tal_arr(tmpctx, struct secret, num); + + /* Randomness, chosen with a fair dice roll! */ + memset(&e, 6, sizeof(e)); + if (!pubkey_from_privkey(&e, &pk_e[0])) + abort(); + + for (size_t i = 0; i < num; i++) { + struct secret ss; + struct secret hmac; + struct sha256 h; + + if (!pubkey_from_hexstr(argv[2+i], + strcspn(argv[2+i], "/"), + &nodes[i])) + errx(1, "%s not a valid pubkey", argv[2+i]); + + if (secp256k1_ecdh(secp256k1_ctx, ss.data, + &nodes[i].pubkey, e.secret.data, NULL, NULL) != 1) + abort(); + + subkey_from_hmac("blinded_node_id", &ss, &hmac); + b[i] = nodes[i]; + if (i != 0) { + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &b[i].pubkey, hmac.data) != 1) + abort(); + } + subkey_from_hmac("rho", &ss, &rho[i]); + h = hash_e_and_ss(&pk_e[i], &ss); + if (i != num-1) + pk_e[i+1] = next_pubkey(&pk_e[i], &h); + e = next_privkey(&e, &h); + } + + /* Print initial blinding factor */ + printf("Blinding: %s\n", + type_to_string(tmpctx, struct pubkey, &pk_e[0])); + + for (size_t i = 0; i < num - 1; i++) { + u8 *p; + u8 buf[BIGSIZE_MAX_LEN]; + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + struct tlv_onionmsg_payload *inner, *outer; + int ret; + + /* Inner is encrypted */ + inner = tlv_onionmsg_payload_new(tmpctx); + /* FIXME: Use /scid for encblob if specified */ + inner->next_node_id = tal(inner, struct tlv_onionmsg_payload_next_node_id); + inner->next_node_id->node_id = nodes[i+1]; + p = tal_arr(tmpctx, u8, 0); + towire_encmsg_tlvs(&p, inner); + + outer = tlv_onionmsg_payload_new(tmpctx); + outer->enctlv = tal(outer, struct tlv_onionmsg_payload_enctlv); + outer->enctlv->enctlv = tal_arr(tmpctx, u8, tal_count(p) + + crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_encrypt(outer->enctlv->enctlv, NULL, + p, + tal_bytelen(p), + NULL, 0, + NULL, npub, + rho[i].data); + assert(ret == 0); + + p = tal_arr(tmpctx, u8, 0); + towire_onionmsg_payload(&p, outer); + ret = bigsize_put(buf, tal_bytelen(p)); + + /* devtools/onion wants length explicitly prepended */ + printf("%s/%.*s%s ", + type_to_string(tmpctx, struct pubkey, &b[i]), + ret * 2, + tal_hexstr(tmpctx, buf, ret), + tal_hex(tmpctx, p)); + } + /* No payload for last node */ + printf("%s/00\n", + type_to_string(tmpctx, struct pubkey, &b[num-1])); + } else if (streq(argv[1], "unwrap")) { + struct privkey privkey; + struct pubkey blinding; + u8 onion[TOTAL_PACKET_SIZE], *dec; + struct onionpacket op; + struct secret ss, onion_ss; + struct secret hmac, rho; + struct route_step *rs; + const u8 *cursor; + struct tlv_onionmsg_payload *outer; + size_t max, len; + struct pubkey res; + struct sha256 h; + int ret; + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (argc != 5) + errx(1, "unwrap requires privkey, onion and blinding"); + + if (!hex_decode(argv[2], strlen(argv[2]), &privkey, + sizeof(privkey))) + errx(1, "Invalid private key hex '%s'", argv[2]); + + if (!hex_decode(argv[3], strlen(argv[3]), onion, + sizeof(onion))) + errx(1, "Invalid onion %s", argv[3]); + + if (!pubkey_from_hexstr(argv[4], strlen(argv[4]), &blinding)) + errx(1, "Invalid blinding %s", argv[4]); + + if (parse_onionpacket(onion, sizeof(onion), &op) != 0) + errx(1, "Unparsable onion"); + + /* ss(r) = H(k(r) * E(r)) */ + if (secp256k1_ecdh(secp256k1_ctx, ss.data, &blinding.pubkey, + privkey.secret.data, NULL, NULL) != 1) + abort(); + + subkey_from_hmac("rho", &ss, &rho); + + /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ + subkey_from_hmac("blinded_node_id", &ss, &hmac); + + /* We instead tweak the *ephemeral* key from the onion + * and use our raw privkey: this models how lightningd + * will do it, since hsmd knows only how to ECDH with + * our real key */ + res = op.ephemeralkey; + if (!first) { + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &res.pubkey, + hmac.data) != 1) + abort(); + } + + if (secp256k1_ecdh(secp256k1_ctx, onion_ss.data, + &res.pubkey, + privkey.secret.data, NULL, NULL) != 1) + abort(); + + rs = process_onionpacket(tmpctx, &op, &onion_ss, NULL, 0, false); + if (!rs) + errx(1, "Could not process onionpacket"); + + cursor = rs->raw_payload; + max = tal_bytelen(cursor); + len = fromwire_bigsize(&cursor, &max); + + /* Always true since we're non-legacy */ + assert(len == max); + outer = tlv_onionmsg_payload_new(tmpctx); + if (!fromwire_onionmsg_payload(&cursor, &max, outer)) + errx(1, "Invalid payload %s", + tal_hex(tmpctx, rs->raw_payload)); + + if (rs->nextcase == ONION_END) { + printf("TERMINAL\n"); + return 0; + } + + /* Look for enctlv */ + if (!outer->enctlv) + errx(1, "No enctlv field"); + + if (tal_bytelen(outer->enctlv->enctlv) + < crypto_aead_chacha20poly1305_ietf_ABYTES) + errx(1, "enctlv field too short"); + + dec = tal_arr(tmpctx, u8, + tal_bytelen(outer->enctlv->enctlv) + - crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_decrypt(dec, NULL, + NULL, + outer->enctlv->enctlv, + tal_bytelen(outer->enctlv->enctlv), + NULL, 0, + npub, + rho.data); + if (ret != 0) + errx(1, "Failed to decrypt enctlv field"); + + printf("Contents: %s\n", tal_hex(tmpctx, dec)); + + /* E(i-1) = H(E(i) || ss(i)) * E(i) */ + h = hash_e_and_ss(&blinding, &ss); + res = next_pubkey(&blinding, &h); + printf("Next blinding: %s\n", + type_to_string(tmpctx, struct pubkey, &res)); + printf("Next onion: %s\n", tal_hex(tmpctx, serialize_onionpacket(tmpctx, rs->next))); + } else + errx(1, "Either create or unwrap!"); +}