diff --git a/lightningd/Makefile b/lightningd/Makefile index e6eeb1d69..00a02a07a 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -27,7 +27,8 @@ LIGHTNINGD_OLD_OBJS := $(LIGHTNINGD_OLD_SRC:.c=.o) LIGHTNINGD_OLD_HEADERS := $(LIGHTNINGD_OLD_SRC:.c=.h) LIGHTNINGD_LIB_SRC := \ - lightningd/cryptomsg.c + lightningd/cryptomsg.c \ + lightningd/key_derive.c LIGHTNINGD_LIB_OBJS := $(LIGHTNINGD_LIB_SRC:.c=.o) LIGHTNINGD_LIB_HEADERS := $(LIGHTNINGD_LIB_SRC:.c=.h) diff --git a/lightningd/key_derive.c b/lightningd/key_derive.c new file mode 100644 index 000000000..808792a0f --- /dev/null +++ b/lightningd/key_derive.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include + +/* BOLT #3: + * + * ### `localkey`, `remotekey`, `local-delayedkey` and `remote-delayedkey` Derivation + * + * These keys are simply generated by addition from their base points: + * + * pubkey = basepoint + SHA256(per-commitment-point || basepoint)*G + * + * The `localkey` uses the local node's `payment-basepoint`, `remotekey` + * uses the remote node's `payment-basepoint`, the `local-delayedkey` + * uses the local node's `delayed-payment-basepoint`, and the + * `remote-delayedkey` uses the remote node's + * `delayed-payment-basepoint`. + */ +bool derive_simple_key(const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct pubkey *key) +{ + struct sha256 sha; + unsigned char der_keys[PUBKEY_DER_LEN * 2]; + + pubkey_to_der(der_keys, per_commitment_point); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, basepoint); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(per-commitment-point || basepoint)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", + tal_hexstr(tmpctx, &sha, sizeof(sha))); +#endif + + *key = *basepoint; + if (secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, + &key->pubkey, sha.u.u8) != 1) + return false; +#ifdef SUPERVERBOSE + printf("# + basepoint (0x%s)\n", + type_to_string(tmpctx, struct pubkey, basepoint)); + printf("# = 0x%s\n", + type_to_string(tmpctx, struct pubkey, key)); +#endif + return true; +} + +/* BOLT #3: + * + * The corresponding private keys can be derived similarly if the basepoint + * secrets are known (i.e., `localkey` and `local-delayedkey` only): + * + * secretkey = basepoint-secret + SHA256(per-commitment-point || basepoint) + */ +bool derive_simple_privkey(const struct privkey *base_secret, + const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct privkey *key) +{ + struct sha256 sha; + unsigned char der_keys[PUBKEY_DER_LEN * 2]; + + pubkey_to_der(der_keys, per_commitment_point); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, basepoint); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(per-commitment-point || basepoint)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", tal_hexstr(tmpctx, &sha, sizeof(sha))); +#endif + + *key = *base_secret; + if (secp256k1_ec_privkey_tweak_add(secp256k1_ctx, key->secret, + sha.u.u8) != 1) + return false; +#ifdef SUPERVERBOSE + printf("# + basepoint_secret (0x%s)\n", + tal_hexstr(tmpctx, base_secret, sizeof(*base_secret))); + printf("# = 0x%s\n", + tal_hexstr(tmpctx, key, sizeof(*key))); +#endif + return true; +} + +/* BOLT #3: + * + * The revocationkey is a blinded key: the remote node provides the base, + * and the local node provides the blinding factor which it later + * reveals, so the remote node can use the secret revocationkey for a + * penalty transaction. + * + * The `per-commitment-point` is generated using EC multiplication: + * + * per-commitment-point = per-commitment-secret * G + * + * And this is used to derive the revocation key from the remote node's + * `revocation-basepoint`: + * + * revocationkey = revocation-basepoint * SHA256(revocation-basepoint || per-commitment-point) + per-commitment-point*SHA256(per-commitment-point || revocation-basepoint) + */ +bool derive_revocation_key(const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct pubkey *key) +{ + struct sha256 sha; + unsigned char der_keys[PUBKEY_DER_LEN * 2]; + secp256k1_pubkey add[2]; + const secp256k1_pubkey *args[2]; + + pubkey_to_der(der_keys, basepoint); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, per_commitment_point); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(revocation-basepoint || per-commitment-point)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", tal_hexstr(tmpctx, sha.u.u8, sizeof(sha.u.u8))), +#endif + + add[0] = basepoint->pubkey; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &add[0], sha.u.u8) != 1) + return false; +#ifdef SUPERVERBOSE + printf("# x revocation-basepoint = 0x%s\n", + type_to_string(tmpctx, secp256k1_pubkey, &add[0])); +#endif + + pubkey_to_der(der_keys, per_commitment_point); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, basepoint); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(per-commitment-point || revocation-basepoint)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", tal_hexstr(tmpctx, sha.u.u8, sizeof(sha.u.u8))), +#endif + + add[1] = per_commitment_point->pubkey; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &add[1], sha.u.u8) != 1) + return false; +#ifdef SUPERVERBOSE + printf("# x per-commitment-point = 0x%s\n", + type_to_string(tmpctx, secp256k1_pubkey, &add[1])); +#endif + + args[0] = &add[0]; + args[1] = &add[1]; + if (secp256k1_ec_pubkey_combine(secp256k1_ctx, &key->pubkey, args, 2) + != 1) + return false; + +#ifdef SUPERVERBOSE + printf("# 0x%s + 0x%s => 0x%s\n", + type_to_string(tmpctx, secp256k1_pubkey, args[0]), + type_to_string(tmpctx, secp256k1_pubkey, args[1]), + type_to_string(tmpctx, struct pubkey, key)); +#endif + return true; +} + +/* BOLT #3: + * + * The corresponding private key can be derived once the `per-commitment-secret` + * is known: + * + * revocationsecretkey = revocation-basepoint-secret * SHA256(revocation-basepoint || per-commitment-point) + per-commitment-secret*SHA256(per-commitment-point || revocation-basepoint) + */ +bool derive_revocation_privkey(const struct privkey *base_secret, + const struct privkey *per_commitment_secret, + const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct privkey *key) +{ + struct sha256 sha; + unsigned char der_keys[PUBKEY_DER_LEN * 2]; + struct privkey part2; + + pubkey_to_der(der_keys, basepoint); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, per_commitment_point); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(revocation-basepoint || per-commitment-point)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", tal_hexstr(tmpctx, sha.u.u8, sizeof(sha.u.u8))), +#endif + + *key = *base_secret; + if (secp256k1_ec_privkey_tweak_mul(secp256k1_ctx, key->secret, sha.u.u8) + != 1) + return false; +#ifdef SUPERVERBOSE + printf("# * revocation-basepoint-secret (0x%s)", + tal_hexstr(tmpctx, base_secret, sizeof(*base_secret))), + printf("# = 0x%s\n", tal_hexstr(tmpctx, key, sizeof(*key))), +#endif + + pubkey_to_der(der_keys, per_commitment_point); + pubkey_to_der(der_keys + PUBKEY_DER_LEN, basepoint); + sha256(&sha, der_keys, sizeof(der_keys)); +#ifdef SUPERVERBOSE + printf("# SHA256(per-commitment-point || revocation-basepoint)\n"); + printf("# => SHA256(0x%s || 0x%s)\n", + tal_hexstr(tmpctx, der_keys, PUBKEY_DER_LEN), + tal_hexstr(tmpctx, der_keys + PUBKEY_DER_LEN, PUBKEY_DER_LEN)); + printf("# = 0x%s\n", tal_hexstr(tmpctx, sha.u.u8, sizeof(sha.u.u8))), +#endif + + part2 = *per_commitment_secret; + if (secp256k1_ec_privkey_tweak_mul(secp256k1_ctx, part2.secret, + sha.u.u8) != 1) + return false; +#ifdef SUPERVERBOSE + printf("# * per-commitment-secret (0x%s)", + tal_hexstr(tmpctx, per_commitment_secret, + sizeof(*per_commitment_secret))), + printf("# = 0x%s\n", tal_hexstr(tmpctx, &part2, sizeof(part2))); +#endif + + if (secp256k1_ec_privkey_tweak_add(secp256k1_ctx, key->secret, + part2.secret) != 1) + return false; + +#ifdef SUPERVERBOSE + printf("# => 0x%s\n", tal_hexstr(tmpctx, key, sizeof(*key))); +#endif + return true; +} diff --git a/lightningd/key_derive.h b/lightningd/key_derive.h new file mode 100644 index 000000000..13e1a7030 --- /dev/null +++ b/lightningd/key_derive.h @@ -0,0 +1,28 @@ +#ifndef LIGHTNING_LIGHTNINGD_KEY_DERIVE_H +#define LIGHTNING_LIGHTNINGD_KEY_DERIVE_H +#include "config.h" + +struct pubkey; + +/* For `localkey`, `remotekey`, `local-delayedkey` and `remote-delayedkey` */ +bool derive_simple_key(const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct pubkey *key); + +bool derive_simple_privkey(const struct privkey *base_secret, + const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct privkey *key); + +/* For `revocationkey` */ +bool derive_revocation_key(const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct pubkey *key); + +bool derive_revocation_privkey(const struct privkey *base_secret, + const struct privkey *per_commitment_secret, + const struct pubkey *basepoint, + const struct pubkey *per_commitment_point, + struct privkey *key); + +#endif /* LIGHTNING_LIGHTNINGD_KEY_DERIVE_H */ diff --git a/lightningd/test/Makefile b/lightningd/test/Makefile index 81de606ed..9bca3d86f 100644 --- a/lightningd/test/Makefile +++ b/lightningd/test/Makefile @@ -8,7 +8,7 @@ LIGHTNINGD_TEST_PROGRAMS := $(LIGHTNINGD_TEST_OBJS:.o=) update-mocks: $(LIGHTNINGD_TEST_SRC:%=update-mocks/%) -$(LIGHTNINGD_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(LIBBASE58_OBJS) $(LIGHTNINGD_HANDSHAKE_GEN_SRC:.c=.o) utils.o libsecp256k1.a libsodium.a +$(LIGHTNINGD_TEST_PROGRAMS): $(CCAN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(LIBBASE58_OBJS) utils.o libsecp256k1.a libsodium.a $(LIGHTNINGD_TEST_OBJS): $(LIGHTNINGD_HEADERS) diff --git a/lightningd/test/run-key_derive.c b/lightningd/test/run-key_derive.c new file mode 100644 index 000000000..31bb32abe --- /dev/null +++ b/lightningd/test/run-key_derive.c @@ -0,0 +1,79 @@ +#define SUPERVERBOSE +static void *tmpctx; + +#include +#include +#include +#include "../key_derive.c" +#include +#include +#include +#include + +static struct privkey privkey_from_hex(const char *hex) +{ + struct privkey privkey; + hex += 2; + if (!hex_decode(hex, strlen(hex), &privkey, sizeof(privkey))) + abort(); + return privkey; +} + +int main(void) +{ + struct privkey base_secret, per_commitment_secret, privkey; + struct pubkey base_point, per_commitment_point, pubkey, pubkey2; + + tmpctx = tal_tmpctx(NULL); + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + + base_secret = privkey_from_hex("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + per_commitment_secret = privkey_from_hex("0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"); + + printf("base_secret: 0x%s\n", + tal_hexstr(tmpctx, &base_secret, sizeof(base_secret))); + printf("per_commitment_secret: 0x%s\n", + tal_hexstr(tmpctx, &per_commitment_secret, + sizeof(per_commitment_secret))); + if (!pubkey_from_privkey(&per_commitment_secret, &per_commitment_point)) + abort(); + if (!pubkey_from_privkey(&base_secret, &base_point)) + abort(); + printf("base_point: 0x%s\n", + type_to_string(tmpctx, struct pubkey, &base_point)); + printf("per_commitment_point: 0x%s\n", + type_to_string(tmpctx, struct pubkey, &per_commitment_point)); + + /* FIXME: Annotate internal steps. */ + if (!derive_simple_key(&base_point, &per_commitment_point, &pubkey)) + abort(); + printf("localkey: 0x%s\n", + type_to_string(tmpctx, struct pubkey, &pubkey)); + if (!derive_simple_privkey(&base_secret, &base_point, + &per_commitment_point, &privkey)) + abort(); + printf("localprivkey: 0x%s\n", + tal_hexstr(tmpctx, &privkey, sizeof(privkey))); + pubkey_from_privkey(&privkey, &pubkey2); + assert(pubkey_eq(&pubkey, &pubkey2)); + + /* FIXME: Annotate internal steps. */ + if (!derive_revocation_key(&base_point, &per_commitment_point, &pubkey)) + abort(); + printf("revocationkey: 0x%s\n", + type_to_string(tmpctx, struct pubkey, &pubkey)); + if (!derive_revocation_privkey(&base_secret, &per_commitment_secret, + &base_point, &per_commitment_point, + &privkey)) + abort(); + printf("revocationprivkey: 0x%s\n", + tal_hexstr(tmpctx, &privkey, sizeof(privkey))); + pubkey_from_privkey(&privkey, &pubkey2); + assert(pubkey_eq(&pubkey, &pubkey2)); + + /* No memory leaks please */ + secp256k1_context_destroy(secp256k1_ctx); + tal_free(tmpctx); + return 0; +}