From 154b917680d8b8f314922b5dbc84f307b9b5fbc0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Oct 2015 15:02:53 +0930 Subject: [PATCH 01/25] test_onion: put padding at the front. This means we can save the partial HMAC of the padding for each step, rather than the padding itself, when generating it. Each step now takes the *last*, not *first* part of the onion array. Signed-off-by: Rusty Russell --- test/test_onion.c | 111 +++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 31dd96750..f2752a613 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -145,16 +145,22 @@ static void gen_keys(secp256k1_context *ctx, #define MAX_HOPS 20 struct hop { - struct sha256 hmac; + unsigned char msg[MESSAGE_SIZE]; /* FIXME: Must use parse/serialize functions. */ secp256k1_pubkey pubkey; - unsigned char msg[MESSAGE_SIZE]; + struct sha256 hmac; }; struct onion { struct hop hop[MAX_HOPS]; }; +/* We peel from the back. */ +static struct hop *myhop(const struct onion *onion) +{ + return (struct hop *)&onion->hop[MAX_HOPS-1]; +} + static bool aes_encrypt(void *dst, const void *src, size_t len, const struct enckey *enckey, const struct iv *iv) { @@ -237,28 +243,26 @@ void dump_contents(const void *data, size_t n) } } -static bool decrypt_padding(struct hop *padding, size_t nhops, - const struct enckey *enckey, - const struct iv *iv) +static bool aes_encrypt_offset(size_t offset, + void *dst, const void *src, size_t len, + const struct enckey *enckey, + const struct iv *iv) { /* * FIXME: This would be easier if we could set the counter; instead - * we simulate it by decrypting junk before the actual padding. + * we simulate it by encrypting junk before the actual data. */ - struct hop tmp[MAX_HOPS]; - + char tmp[offset + len]; + /* Keep valgrind happy. */ - memset(tmp, 0, (MAX_HOPS - nhops) * sizeof(struct hop)); + memset(tmp, 0, offset); + memcpy(tmp + offset, src, len); - memcpy(tmp + MAX_HOPS - nhops, padding, nhops * sizeof(struct hop)); - - /* FIXME: Assumes we are allowed to decrypt in place! */ - if (!aes_decrypt((char *)tmp + offsetof(struct hop, msg), - (char *)tmp + offsetof(struct hop, msg), - sizeof(tmp) - offsetof(struct hop, msg), enckey, iv)) + /* FIXME: Assumes we are allowed to encrypt in place! */ + if (!aes_encrypt(tmp, tmp, offset+len, enckey, iv)) return false; - memcpy(padding, tmp + MAX_HOPS - nhops, nhops * sizeof(struct hop)); + memcpy(dst, tmp + offset, len); return true; } @@ -292,15 +296,14 @@ static void make_hmac(const struct hop *hops, size_t num_hops, HMAC_CTX ctx; size_t len, padlen; - /* Calculate HMAC of pubkey onwards, plus padding. */ + /* Calculate HMAC of padding then onion up to and including pubkey. */ HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, memcheck(hmackey->k.u.u8, sizeof(hmackey->k)), sizeof(hmackey->k), EVP_sha256(), NULL); - len = num_hops*sizeof(struct hop) - offsetof(struct hop, pubkey); - HMAC_Update(&ctx, memcheck((unsigned char *)hops + offsetof(struct hop, pubkey), - len), len); padlen = (MAX_HOPS - num_hops) * sizeof(struct hop); HMAC_Update(&ctx, memcheck((unsigned char *)padding, padlen), padlen); + len = num_hops*sizeof(struct hop) - sizeof(hops->hmac); + HMAC_Update(&ctx, memcheck((unsigned char *)hops, len), len); HMAC_Final(&ctx, hmac->u.u8, NULL); #endif } @@ -310,7 +313,7 @@ static bool check_hmac(struct onion *onion, const struct hmackey *hmackey) struct sha256 hmac; make_hmac(onion->hop, MAX_HOPS, NULL, hmackey, &hmac); - return CRYPTO_memcmp(&hmac, &onion->hop[0].hmac, sizeof(hmac)) == 0; + return CRYPTO_memcmp(&hmac, &myhop(onion)->hmac, sizeof(hmac)) == 0; } bool create_onion(const secp256k1_pubkey pubkey[], @@ -364,13 +367,13 @@ bool create_onion(const secp256k1_pubkey pubkey[], padding[i] = tal_arr(padding, struct hop, i); /* Copy padding from previous node. */ - memcpy(padding[i], padding[i-1], sizeof(struct hop)*(i-1)); /* Previous node "decrypts" it before handing to us */ - if (!decrypt_padding(padding[i], i-1, - &enckeys[i-1], &ivs[i-1])) + if (!aes_decrypt(padding[i]+1, padding[i-1], + sizeof(struct hop)*(i-1), + &enckeys[i-1], &ivs[i-1])) goto fail; /* And generates another lot of padding. */ - add_padding(padding[i]+i-1, &enckeys[i-1], &pad_ivs[i-1]); + add_padding(padding[i], &enckeys[i-1], &pad_ivs[i-1]); } /* @@ -383,37 +386,42 @@ bool create_onion(const secp256k1_pubkey pubkey[], for (i = num - 1; i >= 0; i--) { size_t other_hops; - struct hop *myonion; + struct hop *myhop; other_hops = num - i - 1 + junk_hops; - myonion = hops[i] = tal_arr(hops, struct hop, 1 + other_hops); + hops[i] = tal_arr(hops, struct hop, other_hops + 1); + + /* Our entry is at tail of onion. */ + myhop = hops[i] + other_hops; if (i == num - 1) { /* Fill with junk. */ - random_bytes(myonion + 1, + random_bytes(hops[i], other_hops * sizeof(struct hop)); } else { /* Copy from next hop. */ - memcpy(myonion + 1, hops[i+1], + memcpy(hops[i], hops[i+1], other_hops * sizeof(struct hop)); } /* Now populate our hop. */ - myonion->pubkey = pubkeys[i]; + myhop->pubkey = pubkeys[i]; /* Set message. */ assert(strlen(msg[i]) < MESSAGE_SIZE); - memset(myonion->msg, 0, MESSAGE_SIZE); - strcpy((char *)myonion->msg, msg[i]); - - /* Encrypt whole thing from message onwards. */ - if (!aes_encrypt(&myonion->msg, &myonion->msg, - (1 + other_hops) * sizeof(struct hop) - - offsetof(struct hop, msg), - &enckeys[i], &ivs[i])) + memset(myhop->msg, 0, MESSAGE_SIZE); + strcpy((char *)myhop->msg, msg[i]); + + /* Encrypt whole thing, including our message, but we + * aware it will be offset by the prepended padding. */ + if (!aes_encrypt_offset(i * sizeof(struct hop), + hops[i], hops[i], + other_hops * sizeof(struct hop) + + sizeof(myhop->msg), + &enckeys[i], &ivs[i])) goto fail; /* HMAC covers entire thing except hmac itself. */ - make_hmac(myonion, 1 + other_hops, padding[i], - &hmackeys[i], &myonion->hmac); + make_hmac(hops[i], other_hops + 1, padding[i], + &hmackeys[i], &myhop->hmac); } /* Transfer results to onion, for first node. */ @@ -443,8 +451,7 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); /* Extract shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &onion->hop[0].pubkey, - myseckey->k.u.u8)) + if (!secp256k1_ecdh(ctx, secret, &myhop(onion)->pubkey,myseckey->k.u.u8)) goto fail; hmackey = hmackey_from_secret(secret); @@ -478,9 +485,11 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, if (!check_hmac(onion, &hmackey)) goto fail; - /* Decrypt everything after pubkey. */ - if (!aes_decrypt(onion->hop[0].msg, onion->hop[0].msg, - sizeof(*onion) - offsetof(struct hop, msg), + /* Decrypt everything up to pubkey. */ + /* FIXME: Assumes we can decrypt in place! */ + if (!aes_decrypt(onion, onion, + sizeof(struct hop) * (MAX_HOPS-1) + + sizeof(myhop(onion)->msg), enckey, &iv)) goto fail; @@ -496,14 +505,14 @@ fail: bool peel_onion(struct onion *onion, const struct enckey *enckey, const struct iv *pad_iv) { - /* Move next one to front. */ - memmove(&onion->hop[0], &onion->hop[1], + /* Move next one to back. */ + memmove(&onion->hop[1], &onion->hop[0], sizeof(*onion) - sizeof(onion->hop[0])); /* Add random-looking (but predictable) padding. */ - memset(&onion->hop[MAX_HOPS-1], 0, sizeof(onion->hop[MAX_HOPS-1])); - return aes_encrypt(&onion->hop[MAX_HOPS-1], &onion->hop[MAX_HOPS-1], - sizeof(onion->hop[MAX_HOPS-1]), enckey, pad_iv); + memset(&onion->hop[0], 0, sizeof(onion->hop[0])); + return aes_encrypt(&onion->hop[0], &onion->hop[0], + sizeof(onion->hop[0]), enckey, pad_iv); } int main(int argc, char *argv[]) @@ -540,7 +549,7 @@ int main(int argc, char *argv[]) printf("Decrypting with key %zi\n", i); if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv, i)) errx(1, "Decrypting onion for hop %zi", i); - if (strcmp((char *)onion.hop[0].msg, msgs[i]) != 0) + if (strcmp((char *)myhop(&onion)->msg, msgs[i]) != 0) errx(1, "Bad message for hop %zi", i); if (!peel_onion(&onion, &enckey, &pad_iv)) errx(1, "Peeling onion for hop %zi", i); From 6aae8d62576ba7920c254382d0829d8b5fe34d49 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Oct 2015 15:03:21 +0930 Subject: [PATCH 02/25] test_onion: keep hmacs rather than padding. Signed-off-by: Rusty Russell --- test/test_onion.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index f2752a613..f41c8ebcd 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -328,7 +328,8 @@ bool create_onion(const secp256k1_pubkey pubkey[], struct hmackey *hmackeys = tal_arr(seckeys, struct hmackey, num); struct iv *ivs = tal_arr(seckeys, struct iv, num); struct iv *pad_ivs = tal_arr(seckeys, struct iv, num); - struct hop **padding = tal_arr(seckeys, struct hop *, num); + HMAC_CTX *padding_hmac = tal_arr(seckeys, HMAC_CTX, num); + struct hop *padding = tal_arr(seckeys, struct hop, num); struct hop **hops = tal_arr(seckeys, struct hop *, num); size_t junk_hops; secp256k1_context *ctx; @@ -362,18 +363,24 @@ bool create_onion(const secp256k1_pubkey pubkey[], * and "decrypted" by the others. So we have to generate that * forwards. */ - for (i = 1; i < num; i++) { - /* Each one has 1 padding from previous. */ - padding[i] = tal_arr(padding, struct hop, i); - - /* Copy padding from previous node. */ - /* Previous node "decrypts" it before handing to us */ - if (!aes_decrypt(padding[i]+1, padding[i-1], - sizeof(struct hop)*(i-1), - &enckeys[i-1], &ivs[i-1])) - goto fail; - /* And generates another lot of padding. */ - add_padding(padding[i], &enckeys[i-1], &pad_ivs[i-1]); + for (i = 0; i < num; i++) { + if (i > 0) { + /* Previous node decrypts padding before passing on. */ + aes_decrypt(padding, padding, sizeof(struct hop)*(i-1), + &enckeys[i-1], &ivs[i-1]); + memmove(padding + 1, padding, + sizeof(struct hop)*(i-1)); + } + /* And generates more padding for next node. */ + add_padding(&padding[0], &enckeys[i-1], &pad_ivs[i-1]); + HMAC_CTX_init(&padding_hmac[i]); + HMAC_Init_ex(&padding_hmac[i], + hmackeys[i].k.u.u8, sizeof(hmackeys[i].k), + EVP_sha256(), NULL); + HMAC_Update(&padding_hmac[i], + memcheck((unsigned char *)padding, + i * sizeof(struct hop)), + i * sizeof(struct hop)); } /* @@ -385,7 +392,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], junk_hops = MAX_HOPS - num; for (i = num - 1; i >= 0; i--) { - size_t other_hops; + size_t other_hops, len; struct hop *myhop; other_hops = num - i - 1 + junk_hops; @@ -420,8 +427,10 @@ bool create_onion(const secp256k1_pubkey pubkey[], goto fail; /* HMAC covers entire thing except hmac itself. */ - make_hmac(hops[i], other_hops + 1, padding[i], - &hmackeys[i], &myhop->hmac); + len = (other_hops + 1)*sizeof(struct hop) - sizeof(myhop->hmac); + HMAC_Update(&padding_hmac[i], + memcheck((unsigned char *)hops[i], len), len); + HMAC_Final(&padding_hmac[i], myhop->hmac.u.u8, NULL); } /* Transfer results to onion, for first node. */ From 90794d8ebf89465011c6f2e51358c8d456bf7efe Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Oct 2015 15:04:04 +0930 Subject: [PATCH 03/25] test_onion: generate onion in place. Rather than keeping each hop, we can generate it in place since we only need the first hop result. Signed-off-by: Rusty Russell --- test/test_onion.c | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index f41c8ebcd..21a7e8c6d 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -330,7 +330,6 @@ bool create_onion(const secp256k1_pubkey pubkey[], struct iv *pad_ivs = tal_arr(seckeys, struct iv, num); HMAC_CTX *padding_hmac = tal_arr(seckeys, HMAC_CTX, num); struct hop *padding = tal_arr(seckeys, struct hop, num); - struct hop **hops = tal_arr(seckeys, struct hop *, num); size_t junk_hops; secp256k1_context *ctx; bool ok = false; @@ -390,25 +389,16 @@ bool create_onion(const secp256k1_pubkey pubkey[], /* Unused hops filled with random, so even recipient can't tell * how many were used. */ junk_hops = MAX_HOPS - num; + random_bytes(onion->hop + num, junk_hops * sizeof(struct hop)); for (i = num - 1; i >= 0; i--) { size_t other_hops, len; struct hop *myhop; other_hops = num - i - 1 + junk_hops; - hops[i] = tal_arr(hops, struct hop, other_hops + 1); /* Our entry is at tail of onion. */ - myhop = hops[i] + other_hops; - if (i == num - 1) { - /* Fill with junk. */ - random_bytes(hops[i], - other_hops * sizeof(struct hop)); - } else { - /* Copy from next hop. */ - memcpy(hops[i], hops[i+1], - other_hops * sizeof(struct hop)); - } + myhop = onion->hop + other_hops; /* Now populate our hop. */ myhop->pubkey = pubkeys[i]; @@ -420,7 +410,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], /* Encrypt whole thing, including our message, but we * aware it will be offset by the prepended padding. */ if (!aes_encrypt_offset(i * sizeof(struct hop), - hops[i], hops[i], + onion, onion, other_hops * sizeof(struct hop) + sizeof(myhop->msg), &enckeys[i], &ivs[i])) @@ -429,15 +419,11 @@ bool create_onion(const secp256k1_pubkey pubkey[], /* HMAC covers entire thing except hmac itself. */ len = (other_hops + 1)*sizeof(struct hop) - sizeof(myhop->hmac); HMAC_Update(&padding_hmac[i], - memcheck((unsigned char *)hops[i], len), len); + memcheck((unsigned char *)onion, len), len); HMAC_Final(&padding_hmac[i], myhop->hmac.u.u8, NULL); } - /* Transfer results to onion, for first node. */ - assert(tal_count(hops[0]) == MAX_HOPS); - memcpy(onion->hop, hops[0], sizeof(onion->hop)); ok = true; - fail: tal_free(seckeys); secp256k1_context_destroy(ctx); From 927bc28c8e38fd0c87f5992907fc4a2f6dbbf08a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Oct 2015 15:04:33 +0930 Subject: [PATCH 04/25] test_onion: always generate 0x2 keys. This means they're 32 bytes, which works better for everything. Signed-off-by: Rusty Russell --- test/test_onion.c | 130 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 11 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 21a7e8c6d..31edec4a3 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -13,6 +13,7 @@ #include #include #include +#include /* * The client knows the server's public key S (which has corresponding @@ -31,7 +32,15 @@ //#define NO_HMAC 1 /* No real hmac */ struct seckey { - struct sha256 k; + union { + unsigned char u8[32]; + beint64_t be64[4]; + } u; +}; + +/* Prepend 0x02 to get pubkey for libsecp256k1 */ +struct pubkey { + unsigned char u8[32]; }; struct enckey { @@ -108,12 +117,97 @@ static void random_bytes(void *dst, size_t n) d[i] = random() % 256; } -static void gen_keys(secp256k1_context *ctx, - struct seckey *seckey, secp256k1_pubkey *pubkey) +/* Compressed key would start with 0x3? Subtract from group. Thanks + * Greg Maxwell. */ +static void flip_key(struct seckey *seckey) +{ + int i; + bool carry = 0; + + const int64_t group[] = { + 0xFFFFFFFFFFFFFFFFULL, + 0xFFFFFFFFFFFFFFFEULL, + 0xBAAEDCE6AF48A03BULL, + 0xBFD25E8CD0364141ULL + }; + + for (i = 3; i >= 0; i--) { + uint64_t v = be64_to_cpu(seckey->u.be64[i]); + if (carry) { + /* Beware wrap if v == 0xFFFF.... */ + carry = (group[i] <= v); + v++; + } else + carry = (group[i] < v); + + v = group[i] - v; + seckey->u.be64[i] = cpu_to_be64(v); + } +} + +#if 0 +int main(int argc, char *argv[]) +{ + struct seckey k; + + k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); + k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); + k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); + k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364141ULL); + flip_key(&k); + assert(k.u.be64[0] == 0); + assert(k.u.be64[1] == 0); + assert(k.u.be64[2] == 0); + assert(k.u.be64[3] == 0); + flip_key(&k); + assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); + assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); + assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); + assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364141ULL)); + + k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); + k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); + k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); + k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364142ULL); + flip_key(&k); + assert(k.u.be64[0] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[1] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[2] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[3] == 0xFFFFFFFFFFFFFFFFULL); + flip_key(&k); + assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); + assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); + assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); + assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364142ULL)); + + return 0; +} +#endif + +static void random_key(secp256k1_context *ctx, + struct seckey *seckey, secp256k1_pubkey *pkey) { do { - random_bytes(seckey->k.u.u8, sizeof(seckey->k)); - } while (!secp256k1_ec_pubkey_create(ctx, pubkey, seckey->k.u.u8)); + random_bytes(seckey->u.u8, sizeof(seckey->u)); + } while (!secp256k1_ec_pubkey_create(ctx, pkey, seckey->u.u8)); +} + +/* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ +static void gen_keys(secp256k1_context *ctx, + struct seckey *seckey, struct pubkey *pubkey) +{ + unsigned char tmp[33]; + secp256k1_pubkey pkey; + size_t len; + + random_key(ctx, seckey, &pkey); + + secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, + SECP256K1_EC_COMPRESSED); + assert(len == sizeof(tmp)); + if (tmp[0] == 0x3) + flip_key(seckey); + memcpy(pubkey, tmp+1, sizeof(*pubkey)); } /* @@ -146,8 +240,7 @@ static void gen_keys(secp256k1_context *ctx, struct hop { unsigned char msg[MESSAGE_SIZE]; - /* FIXME: Must use parse/serialize functions. */ - secp256k1_pubkey pubkey; + struct pubkey pubkey; struct sha256 hmac; }; @@ -323,7 +416,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], { int i; struct seckey *seckeys = tal_arr(NULL, struct seckey, num); - secp256k1_pubkey *pubkeys = tal_arr(seckeys, secp256k1_pubkey, num); + struct pubkey *pubkeys = tal_arr(seckeys, struct pubkey, num); struct enckey *enckeys = tal_arr(seckeys, struct enckey, num); struct hmackey *hmackeys = tal_arr(seckeys, struct hmackey, num); struct iv *ivs = tal_arr(seckeys, struct iv, num); @@ -346,7 +439,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], gen_keys(ctx, &seckeys[i], &pubkeys[i]); /* Make shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &pubkey[i], seckeys[i].k.u.u8)) + if (!secp256k1_ecdh(ctx, secret, &pubkey[i], seckeys[i].u.u8)) goto fail; hmackeys[i] = hmackey_from_secret(memcheck(secret, 32)); @@ -430,6 +523,17 @@ fail: return ok; } +static bool pubkey_parse(const secp256k1_context *ctx, + secp256k1_pubkey* pubkey, + struct pubkey *pkey) +{ + unsigned char tmp[33]; + + tmp[0] = 0x2; + memcpy(tmp+1, pkey, sizeof(*pkey)); + return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); +} + /* * Decrypt onion, return true if onion->hop[0] is valid. * @@ -442,11 +546,15 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, unsigned char secret[32]; struct hmackey hmackey; struct iv iv; + secp256k1_pubkey pubkey; ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + if (!pubkey_parse(ctx, &pubkey, &myhop(onion)->pubkey)) + goto fail; + /* Extract shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &myhop(onion)->pubkey,myseckey->k.u.u8)) + if (!secp256k1_ecdh(ctx, secret, &pubkey, myseckey->u.u8)) goto fail; hmackey = hmackey_from_secret(secret); @@ -530,7 +638,7 @@ int main(int argc, char *argv[]) ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); for (i = 0; i < hops; i++) { asprintf(&msgs[i], "Message to %zu", i); - gen_keys(ctx, &seckeys[i], &pubkeys[i]); + random_key(ctx, &seckeys[i], &pubkeys[i]); } if (!create_onion(pubkeys, msgs, hops, &onion)) From e165d0009cc3da196aa5eb29d443bfdd183ae580 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Oct 2015 15:16:44 +0930 Subject: [PATCH 05/25] test_onion: Switch from AES256 to AES128. AFAICT, if SHA256 is good enough, and secp256k1 is good enough, AES128 is good enough. Signed-off-by: Rusty Russell --- test/test_onion.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 31edec4a3..76741adf4 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -270,7 +270,7 @@ static bool aes_encrypt(void *dst, const void *src, size_t len, int outlen; /* Counter mode allows parallelism in future. */ - if (EVP_EncryptInit(&evpctx, EVP_aes_256_ctr(), + if (EVP_EncryptInit(&evpctx, EVP_aes_128_ctr(), memcheck(enckey->k.u.u8, sizeof(enckey->k)), memcheck(iv->iv, sizeof(iv->iv))) != 1) return false; @@ -305,7 +305,7 @@ static bool aes_decrypt(void *dst, const void *src, size_t len, int outlen; /* Counter mode allows parallelism in future. */ - if (EVP_DecryptInit(&evpctx, EVP_aes_256_ctr(), + if (EVP_DecryptInit(&evpctx, EVP_aes_128_ctr(), memcheck(enckey->k.u.u8, sizeof(enckey->k)), memcheck(iv->iv, sizeof(iv->iv))) != 1) return false; @@ -627,7 +627,7 @@ int main(int argc, char *argv[]) char *msgs[MAX_HOPS]; struct onion onion; - assert(EVP_CIPHER_iv_length(EVP_aes_256_ctr()) == sizeof(struct iv)); + assert(EVP_CIPHER_iv_length(EVP_aes_128_ctr()) == sizeof(struct iv)); if (argc != 2) errx(1, "Usage: %s ", argv[0]); From b2c86c650a45d232f46aa116ba66974718deb2d9 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 4 Oct 2015 15:02:30 +1000 Subject: [PATCH 06/25] test_onion: dump more output --- test/test_onion.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/test_onion.c b/test/test_onion.c index 76741adf4..0fd9e6845 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -401,6 +401,21 @@ static void make_hmac(const struct hop *hops, size_t num_hops, #endif } +void _dump_hex(unsigned char *x, size_t s) { + printf(" "); + while (s > 0) { + printf("%02x", *x); + x++; s--; + } +} +#define dump_hex(x) _dump_hex((void*)&x, sizeof(x)) +void dump_pkey(secp256k1_context *ctx, secp256k1_pubkey pkey) { + unsigned char tmp[65]; + size_t len; + secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, 0); + dump_hex(tmp); +} + static bool check_hmac(struct onion *onion, const struct hmackey *hmackey) { struct sha256 hmac; @@ -438,6 +453,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], gen_keys(ctx, &seckeys[i], &pubkeys[i]); + /* Make shared secret. */ if (!secp256k1_ecdh(ctx, secret, &pubkey[i], seckeys[i].u.u8)) goto fail; @@ -639,10 +655,15 @@ int main(int argc, char *argv[]) for (i = 0; i < hops; i++) { asprintf(&msgs[i], "Message to %zu", i); random_key(ctx, &seckeys[i], &pubkeys[i]); + printf(" * Keypair %zu:", i); + dump_hex(seckeys[i]); + dump_pkey(ctx, pubkeys[i]); + printf("\n"); } if (!create_onion(pubkeys, msgs, hops, &onion)) errx(1, "Creating onion packet failed"); + printf(" * Message:"); dump_hex(onion); printf("\n"); /* Now parse and peel. */ for (i = 0; i < hops; i++) { @@ -650,6 +671,7 @@ int main(int argc, char *argv[]) struct iv pad_iv; printf("Decrypting with key %zi\n", i); + if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv, i)) errx(1, "Decrypting onion for hop %zi", i); if (strcmp((char *)myhop(&onion)->msg, msgs[i]) != 0) From 75dceaf25452df8ff9ac92e2cd6b538322558639 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 4 Oct 2015 15:02:51 +1000 Subject: [PATCH 07/25] test_onion.py: alternative onion peeling implementation --- test/test_onion.py | 146 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 test/test_onion.py diff --git a/test/test_onion.py b/test/test_onion.py new file mode 100644 index 000000000..9e3bce53f --- /dev/null +++ b/test/test_onion.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +import sys + +from pyelliptic import ecc +from pyelliptic import Cipher +from pyelliptic.hash import hmac_sha256 +from hashlib import sha256 + +hexlify = ecc.hexlify +unhexlify = ecc.unhexlify + +## pyelliptic doesn't support compressed pubkey representations +## so we have to add some code... +from pyelliptic.openssl import OpenSSL +import ctypes + +OpenSSL.EC_POINT_set_compressed_coordinates_GFp = \ + OpenSSL._lib.EC_POINT_set_compressed_coordinates_GFp +OpenSSL.EC_POINT_set_compressed_coordinates_GFp.restype = ctypes.c_int +OpenSSL.EC_POINT_set_compressed_coordinates_GFp.argtypes = [ + ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, + ctypes.c_void_p] + +def get_pos_y_for_x(pubkey_x, yneg=0): + key = pub_key = pub_key_x = pub_key_y = None + try: + key = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) + group = OpenSSL.EC_KEY_get0_group(key) + pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + pub_key = OpenSSL.EC_POINT_new(group) + + if OpenSSL.EC_POINT_set_compressed_coordinates_GFp(group, pub_key, + pub_key_x, yneg, 0) == 0: + raise Exception("[OpenSSL] EC_POINT_set_compressed_coordinates_GFp FAIL ... " + OpenSSL.get_error()) + + + pub_key_y = OpenSSL.BN_new() + if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, 0 + )) == 0: + raise Exception("[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ... " + OpenSSL.get_error()) + + pubkeyy = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(pub_key_y)) + OpenSSL.BN_bn2bin(pub_key_y, pubkeyy) + pubkeyy = pubkeyy.raw + field_size = OpenSSL.EC_GROUP_get_degree(OpenSSL.EC_KEY_get0_group(key)) + secret_len = int((field_size + 7) / 8) + if len(pubkeyy) < secret_len: + pubkeyy = pubkeyy.rjust(secret_len, b'\0') + return pubkeyy + finally: + if key is not None: OpenSSL.EC_KEY_free(key) + if pub_key is not None: OpenSSL.EC_POINT_free(pub_key) + if pub_key_x is not None: OpenSSL.BN_free(pub_key_x) + if pub_key_y is not None: OpenSSL.BN_free(pub_key_y) + +class Onion(object): + HMAC_LEN = 32 + PKEY_LEN = 32 + MSG_LEN = 128 + ZEROES = b"\x00" * (HMAC_LEN + PKEY_LEN + MSG_LEN) + + def __init__(self, onion, my_ecc): + self.my_ecc = my_ecc + + hmac_end = len(onion) + pkey_end = hmac_end - self.HMAC_LEN + self.msg_end = pkey_end - self.PKEY_LEN + self.fwd_end = self.msg_end - self.MSG_LEN + + self.onion = onion + self.pkey = onion[self.msg_end:pkey_end] + self.hmac = onion[pkey_end:hmac_end] + + self.get_secrets() + + def padding(self): + ctx = Cipher(self.enckey, self.pad_iv, 1, ciphername='aes-128-ctr') + self.pad = ctx.ciphering(self.ZEROES) + + def decrypt(self): + self.padding() + + ctx = Cipher(self.enckey, self.iv, 0, ciphername='aes-128-ctr') + self.fwd = self.pad + ctx.ciphering(self.onion[:self.fwd_end]) + self.msg = ctx.ciphering(self.onion[self.fwd_end:self.msg_end]) + + def tweak_sha(self, sha, d): + sha = sha.copy() + sha.update(d) + return sha.digest() + + def get_secrets(self): + pkey_x = self.pkey + pkey_y = get_pos_y_for_x(pkey_x) + pkey = unhexlify('04') + pkey_x + pkey_y + tmp_key = ecc.ECC(curve='secp256k1', pubkey=pkey) + sec_x = self.my_ecc.get_ecdh_key(tmp_key.get_pubkey()) + sec_1 = sha256(sha256(b"\x02" + sec_x).digest()) + sec_2 = sha256(sha256(b"\x03" + sec_x).digest()) + + sec = None + if self.check_hmac(self.tweak_sha(sec_1, b'\x01')): + sec = sec_1 + if self.check_hmac(self.tweak_sha(sec_2, b'\x01')): + sec = sec_2 + if sec is None: + raise Exception("HMAC did not verify") + + self.enckey = self.tweak_sha(sec, b'\x00') + self.iv = self.tweak_sha(sec, b'\x02') + self.pad_iv = self.tweak_sha(sec, b'\x03') + + def check_hmac(self, hmac_key): + calc = hmac_sha256(hmac_key, self.onion[:-self.HMAC_LEN]) + return calc == self.hmac + +if __name__ == "__main__": + keys = [] + msg = "" + for ln in sys.stdin.readlines(): + if ln.startswith(" * Keypair "): + w = ln.strip().split() + idx = int(w[2].strip(":")) + priv = unhexlify(w[3]) + pub = unhexlify(w[4]) + assert idx == len(keys) + keys.append(ecc.ECC(privkey=priv, pubkey=pub, curve='secp256k1')) + elif ln.startswith(" * Message:"): + msg = unhexlify(ln[11:].strip()) + elif ln.startswith("Decrypting"): + pass + else: + print ln + assert ln.strip() == "" + + assert msg != "" + for k in keys: + o = Onion(msg, k) + o.decrypt() + print o.msg + msg = o.fwd + + print "done" From 53e13e69c9a0af817ff46d5f689736b28e9017af Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 4 Oct 2015 15:21:06 +1000 Subject: [PATCH 08/25] test_onion.py: drop separate padding method --- test/test_onion.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/test_onion.py b/test/test_onion.py index 9e3bce53f..1a38f6cd0 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -76,15 +76,12 @@ class Onion(object): self.get_secrets() - def padding(self): - ctx = Cipher(self.enckey, self.pad_iv, 1, ciphername='aes-128-ctr') - self.pad = ctx.ciphering(self.ZEROES) - def decrypt(self): - self.padding() + ctx = Cipher(self.enckey, self.pad_iv, 1, ciphername='aes-128-ctr') + pad = ctx.ciphering(self.ZEROES) ctx = Cipher(self.enckey, self.iv, 0, ciphername='aes-128-ctr') - self.fwd = self.pad + ctx.ciphering(self.onion[:self.fwd_end]) + self.fwd = pad + ctx.ciphering(self.onion[:self.fwd_end]) self.msg = ctx.ciphering(self.onion[self.fwd_end:self.msg_end]) def tweak_sha(self, sha, d): From bb26fc302681fa5820dca9ff5f47de414c0a3564 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Mon, 5 Oct 2015 17:44:49 +1000 Subject: [PATCH 09/25] test_onion.py: drop unused part of message secrets --- test/test_onion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_onion.py b/test/test_onion.py index 1a38f6cd0..3c6067db1 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -106,9 +106,9 @@ class Onion(object): if sec is None: raise Exception("HMAC did not verify") - self.enckey = self.tweak_sha(sec, b'\x00') - self.iv = self.tweak_sha(sec, b'\x02') - self.pad_iv = self.tweak_sha(sec, b'\x03') + self.enckey = self.tweak_sha(sec, b'\x00')[:16] + self.iv = self.tweak_sha(sec, b'\x02')[:16] + self.pad_iv = self.tweak_sha(sec, b'\x03')[:16] def check_hmac(self, hmac_key): calc = hmac_sha256(hmac_key, self.onion[:-self.HMAC_LEN]) From 8b0635f7d38d72ffccd8a607ab269287f679aca6 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 00:44:03 +1000 Subject: [PATCH 10/25] test_onion.py: make it possible to build an onion switched from pyelliptic to hmac/binascii/cryptography for standard functions use our own ECDH implementation to better match the one from secp256k1 finally, add function to create an encrypted onion --- test/test_onion.py | 258 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 222 insertions(+), 36 deletions(-) diff --git a/test/test_onion.py b/test/test_onion.py index 3c6067db1..91d818ed4 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -2,13 +2,28 @@ import sys -from pyelliptic import ecc -from pyelliptic import Cipher -from pyelliptic.hash import hmac_sha256 from hashlib import sha256 +from binascii import hexlify, unhexlify +import hmac +import random + +from cryptography.hazmat.primitives.ciphers import Cipher, modes, algorithms +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import CTR +from cryptography.hazmat.backends import default_backend +# http://cryptography.io + +from pyelliptic import ecc + +class MyEx(Exception): pass + +def hmac_sha256(k, m): + return hmac.new(k, m, sha256).digest() + + + + -hexlify = ecc.hexlify -unhexlify = ecc.unhexlify ## pyelliptic doesn't support compressed pubkey representations ## so we have to add some code... @@ -22,6 +37,74 @@ OpenSSL.EC_POINT_set_compressed_coordinates_GFp.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] +def ecc_ecdh_key(sec, pub): + assert isinstance(sec, ecc.ECC) + if isinstance(pub, ecc.ECC): + pub = pub.get_pubkey() + #return sec.get_ecdh_key(pub) + + pubkey_x, pubkey_y = ecc.ECC._decode_pubkey(pub, 'binary') + + other_key = other_pub_key_x = other_pub_key_y = other_pub_key = None + own_priv_key = res = res_x = res_y = None + try: + other_key = OpenSSL.EC_KEY_new_by_curve_name(sec.curve) + if other_key == 0: + raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ... " + OpenSSL.get_error()) + + other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) + + other_group = OpenSSL.EC_KEY_get0_group(other_key) + other_pub_key = OpenSSL.EC_POINT_new(other_group) + if (other_pub_key == None): + raise Exception("[OpenSSl] EC_POINT_new FAIL ... " + OpenSSL.get_error()) + + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, + other_pub_key, + other_pub_key_x, + other_pub_key_y, + 0)) == 0: + raise Exception( + "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ..." + OpenSSL.get_error()) + + own_priv_key = OpenSSL.BN_bin2bn(sec.privkey, len(sec.privkey), 0) + + res = OpenSSL.EC_POINT_new(other_group) + if (OpenSSL.EC_POINT_mul(other_group, res, 0, other_pub_key, own_priv_key, 0)) == 0: + raise Exception( + "[OpenSSL] EC_POINT_mul FAIL ..." + OpenSSL.get_error()) + + res_x = OpenSSL.BN_new() + res_y = OpenSSL.BN_new() + + if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(other_group, res, + res_x, + res_y, 0 + )) == 0: + raise Exception( + "[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ... " + OpenSSL.get_error()) + + resx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(res_x)) + resy = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(res_y)) + + OpenSSL.BN_bn2bin(res_x, resx) + resx = resx.raw + OpenSSL.BN_bn2bin(res_y, resy) + resy = resy.raw + + return resx, resy + + finally: + if other_key: OpenSSL.EC_KEY_free(other_key) + if other_pub_key_x: OpenSSL.BN_free(other_pub_key_x) + if other_pub_key_y: OpenSSL.BN_free(other_pub_key_y) + if other_pub_key: OpenSSL.EC_POINT_free(other_pub_key) + if own_priv_key: OpenSSL.BN_free(own_priv_key) + if res: OpenSSL.EC_POINT_free(res) + if res_x: OpenSSL.BN_free(res_x) + if res_y: OpenSSL.BN_free(res_y) + def get_pos_y_for_x(pubkey_x, yneg=0): key = pub_key = pub_key_x = pub_key_y = None try: @@ -62,6 +145,34 @@ class Onion(object): MSG_LEN = 128 ZEROES = b"\x00" * (HMAC_LEN + PKEY_LEN + MSG_LEN) + @staticmethod + def tweak_sha(sha, d): + sha = sha.copy() + sha.update(d) + return sha.digest() + + @classmethod + def get_ecdh_secrets(cls, sec, pkey_x, pkey_y): + pkey = unhexlify('04') + pkey_x + pkey_y + tmp_key = ecc.ECC(curve='secp256k1', pubkey=pkey) + sec_x, sec_y = ecc_ecdh_key(sec, tmp_key) + + b = '\x02' if ord(sec_y[-1]) % 2 == 0 else '\x03' + sec = sha256(sha256(b + sec_x).digest()) + + enckey = cls.tweak_sha(sec, b'\x00')[:16] + hmac = cls.tweak_sha(sec, b'\x01') + iv = cls.tweak_sha(sec, b'\x02')[:16] + pad_iv = cls.tweak_sha(sec, b'\x03')[:16] + + return enckey, hmac, iv, pad_iv + + def enc_pad(self, enckey, pad_iv): + aes = Cipher(AES(enckey), CTR(pad_iv), + default_backend()).encryptor() + return aes.update(self.ZEROES) + +class OnionDecrypt(Onion): def __init__(self, onion, my_ecc): self.my_ecc = my_ecc @@ -77,47 +188,104 @@ class Onion(object): self.get_secrets() def decrypt(self): - ctx = Cipher(self.enckey, self.pad_iv, 1, ciphername='aes-128-ctr') - pad = ctx.ciphering(self.ZEROES) + pad = self.enc_pad(self.enckey, self.pad_iv) - ctx = Cipher(self.enckey, self.iv, 0, ciphername='aes-128-ctr') - self.fwd = pad + ctx.ciphering(self.onion[:self.fwd_end]) - self.msg = ctx.ciphering(self.onion[self.fwd_end:self.msg_end]) - - def tweak_sha(self, sha, d): - sha = sha.copy() - sha.update(d) - return sha.digest() + aes = Cipher(AES(self.enckey), CTR(self.iv), + default_backend()).decryptor() + self.fwd = pad + aes.update(self.onion[:self.fwd_end]) + self.msg = aes.update(self.onion[self.fwd_end:self.msg_end]) def get_secrets(self): pkey_x = self.pkey - pkey_y = get_pos_y_for_x(pkey_x) - pkey = unhexlify('04') + pkey_x + pkey_y - tmp_key = ecc.ECC(curve='secp256k1', pubkey=pkey) - sec_x = self.my_ecc.get_ecdh_key(tmp_key.get_pubkey()) - sec_1 = sha256(sha256(b"\x02" + sec_x).digest()) - sec_2 = sha256(sha256(b"\x03" + sec_x).digest()) - - sec = None - if self.check_hmac(self.tweak_sha(sec_1, b'\x01')): - sec = sec_1 - if self.check_hmac(self.tweak_sha(sec_2, b'\x01')): - sec = sec_2 - if sec is None: + pkey_y = get_pos_y_for_x(pkey_x) # always positive by design + enckey, hmac, iv, pad_iv = self.get_ecdh_secrets(self.my_ecc, pkey_x, pkey_y) + if not self.check_hmac(hmac): raise Exception("HMAC did not verify") - - self.enckey = self.tweak_sha(sec, b'\x00')[:16] - self.iv = self.tweak_sha(sec, b'\x02')[:16] - self.pad_iv = self.tweak_sha(sec, b'\x03')[:16] + self.enckey = enckey + self.iv = iv + self.pad_iv = pad_iv def check_hmac(self, hmac_key): calc = hmac_sha256(hmac_key, self.onion[:-self.HMAC_LEN]) return calc == self.hmac -if __name__ == "__main__": +class OnionEncrypt(Onion): + def __init__(self, msgs, pubkeys): + assert len(msgs) == len(pubkeys) + assert 0 < len(msgs) <= 20 + assert all( len(m) <= self.MSG_LEN for m in msgs ) + + msgs = [m + "\0"*(self.MSG_LEN - len(m)) for m in msgs] + pubkeys = [ecc.ECC(pubkey=pk, curve='secp256k1') for pk in pubkeys] + n = len(msgs) + + tmpkeys = [] + tmppubkeys = [] + for i in range(n): + while True: + t = ecc.ECC(curve='secp256k1') + if ord(t.pubkey_y[-1]) % 2 == 0: + break + # or do the math to "flip" the secret key and pub key + tmpkeys.append(t) + tmppubkeys.append(t.pubkey_x) + + enckeys, hmacs, ivs, pad_ivs = zip(*[self.get_ecdh_secrets(tmpkey, pkey.pubkey_x, pkey.pubkey_y) + for tmpkey, pkey in zip(tmpkeys, pubkeys)]) + + # padding takes the form: + # E_(n-1)(0000s) + # D_(n-1)( + # E(n-2)(0000s) + # D(n-2)( + # ... + # ) + # ) + + padding = "" + for i in range(n-1): + pad = self.enc_pad(enckeys[i], pad_ivs[i]) + aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), + default_backend()).decryptor() + padding = pad + aes.update(padding) + + if n < 20: + padding += str(bytearray(random.getrandbits(8) + for _ in range(len(self.ZEROES) * (20-n)))) + + # to encrypt the message we need to bump the counter past all + # the padding, then just encrypt the final message + aes = Cipher(AES(enckeys[-1]), CTR(ivs[-1]), + default_backend()).encryptor() + aes.update(padding) # don't care about cyphertext + msgenc = aes.update(msgs[-1]) + + msgenc = padding + msgenc + tmppubkeys[-1] + del padding + msgenc += hmac_sha256(hmacs[-1], msgenc) + + # *PHEW* + # now iterate + + for i in reversed(range(n-1)): + # drop the padding this node will add + msgenc = msgenc[len(self.ZEROES):] + # adding the msg + msgenc += msgs[i] + # encrypt it + aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), + default_backend()).encryptor() + msgenc = aes.update(msgenc) + # add the tmp key + msgenc += tmppubkeys[i] + # add the hmac + msgenc += hmac_sha256(hmacs[i], msgenc) + self.onion = msgenc + +def decode_from_file(f): keys = [] msg = "" - for ln in sys.stdin.readlines(): + for ln in f.readlines(): if ln.startswith(" * Keypair "): w = ln.strip().split() idx = int(w[2].strip(":")) @@ -135,9 +303,27 @@ if __name__ == "__main__": assert msg != "" for k in keys: - o = Onion(msg, k) + o = OnionDecrypt(msg, k) o.decrypt() print o.msg msg = o.fwd - print "done" + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "generate": + if len(sys.argv) == 3: + n = int(sys.argv[2]) + else: + n = 20 + servers = [ecc.ECC(curve='secp256k1') for _ in range(n)] + server_pubs = [s.get_pubkey() for s in servers] + msgs = ["Howzit %d..." % (i,) for i in range(n)] + + o = OnionEncrypt(msgs, server_pubs) + + for i, s in enumerate(servers): + print " * Keypair %d: %s %s" % ( + i, hexlify(s.privkey), hexlify(s.get_pubkey())) + print " * Message: %s" % (hexlify(o.onion)) + else: + decode_from_file(sys.stdin) From f69306006853f02cf3ea689ee012fefddba1ed6e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:00:12 +1030 Subject: [PATCH 11/25] test_onion: fix random padding. Randomness is now at start; thanks valgrind! Signed-off-by: Rusty Russell --- test/test_onion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_onion.c b/test/test_onion.c index 0fd9e6845..a655e6aa6 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -498,7 +498,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], /* Unused hops filled with random, so even recipient can't tell * how many were used. */ junk_hops = MAX_HOPS - num; - random_bytes(onion->hop + num, junk_hops * sizeof(struct hop)); + random_bytes(onion->hop, junk_hops * sizeof(struct hop)); for (i = num - 1; i >= 0; i--) { size_t other_hops, len; From 8e9944bc37bec3f4e4cb7687d7f633578c9ee803 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:00:26 +1030 Subject: [PATCH 12/25] test: add .gitignore Signed-off-by: Rusty Russell --- test/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/.gitignore diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..4c5814c98 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +test_onion +test_state_coverage From 9aa8907e38ad1dff6dd6baf5cbd1097a2d682bec Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:03:03 +1030 Subject: [PATCH 13/25] test_onion: Rename struct pubkey to struct onion_pubkey. And move to onion_key.h for next patch. Signed-off-by: Rusty Russell --- test/onion_key.h | 17 +++++++++++++++++ test/test_onion.c | 21 +++++---------------- 2 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 test/onion_key.h diff --git a/test/onion_key.h b/test/onion_key.h new file mode 100644 index 000000000..9fd0b5a4f --- /dev/null +++ b/test/onion_key.h @@ -0,0 +1,17 @@ +#ifndef ONION_KEY_H +#define ONION_KEY_H +#include + +struct seckey { + union { + unsigned char u8[32]; + beint64_t be64[4]; + } u; +}; + +/* Prepend 0x02 to get pubkey for libsecp256k1 */ +struct onion_pubkey { + unsigned char u8[32]; +}; + +#endif /* ONION_KEY_H */ diff --git a/test/test_onion.c b/test/test_onion.c index a655e6aa6..9a2326fca 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE 1 +#include "onion_key.h" #include "secp256k1.h" #include "secp256k1_ecdh.h" #include @@ -31,18 +32,6 @@ //#define EXPORT_FRIENDLY 1 /* No crypto! */ //#define NO_HMAC 1 /* No real hmac */ -struct seckey { - union { - unsigned char u8[32]; - beint64_t be64[4]; - } u; -}; - -/* Prepend 0x02 to get pubkey for libsecp256k1 */ -struct pubkey { - unsigned char u8[32]; -}; - struct enckey { struct sha256 k; }; @@ -194,7 +183,7 @@ static void random_key(secp256k1_context *ctx, /* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ static void gen_keys(secp256k1_context *ctx, - struct seckey *seckey, struct pubkey *pubkey) + struct seckey *seckey, struct onion_pubkey *pubkey) { unsigned char tmp[33]; secp256k1_pubkey pkey; @@ -240,7 +229,7 @@ static void gen_keys(secp256k1_context *ctx, struct hop { unsigned char msg[MESSAGE_SIZE]; - struct pubkey pubkey; + struct onion_pubkey pubkey; struct sha256 hmac; }; @@ -431,7 +420,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], { int i; struct seckey *seckeys = tal_arr(NULL, struct seckey, num); - struct pubkey *pubkeys = tal_arr(seckeys, struct pubkey, num); + struct onion_pubkey *pubkeys = tal_arr(seckeys, struct onion_pubkey, num); struct enckey *enckeys = tal_arr(seckeys, struct enckey, num); struct hmackey *hmackeys = tal_arr(seckeys, struct hmackey, num); struct iv *ivs = tal_arr(seckeys, struct iv, num); @@ -541,7 +530,7 @@ fail: static bool pubkey_parse(const secp256k1_context *ctx, secp256k1_pubkey* pubkey, - struct pubkey *pkey) + struct onion_pubkey *pkey) { unsigned char tmp[33]; From 32a08ce6c54b0f2f9138b7ae2cc647f7e6eef4c2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:03:09 +1030 Subject: [PATCH 14/25] test/onion_key: helper to generate deterministic key pairs. Signed-off-by: Rusty Russell --- Makefile | 1 + test/.gitignore | 1 + test/onion_key.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 test/onion_key.c diff --git a/Makefile b/Makefile index 2509cb6cc..f95b4d557 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ TEST_CLI_PROGRAMS := \ TEST_PROGRAMS := \ test/test_state_coverage \ + test/onion_key \ test/test_onion BITCOIN_OBJS := \ diff --git a/test/.gitignore b/test/.gitignore index 4c5814c98..4dce2dd2a 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,3 @@ test_onion test_state_coverage +onion_key diff --git a/test/onion_key.c b/test/onion_key.c new file mode 100644 index 000000000..c6ea69c57 --- /dev/null +++ b/test/onion_key.c @@ -0,0 +1,136 @@ +#define _GNU_SOURCE 1 +#include "secp256k1.h" +#include "secp256k1_ecdh.h" +#include "onion_key.h" +#include +#include +#include +#include +#include +#include +#include + +/* Not really! */ +static void random_bytes(void *dst, size_t n) +{ + size_t i; + unsigned char *d = dst; + + for (i = 0; i < n; i++) + d[i] = random() % 256; +} + +/* Compressed key would start with 0x3? Subtract from group. Thanks + * Greg Maxwell. */ +static void flip_key(struct seckey *seckey) +{ + int i; + bool carry = 0; + + const int64_t group[] = { + 0xFFFFFFFFFFFFFFFFULL, + 0xFFFFFFFFFFFFFFFEULL, + 0xBAAEDCE6AF48A03BULL, + 0xBFD25E8CD0364141ULL + }; + + for (i = 3; i >= 0; i--) { + uint64_t v = be64_to_cpu(seckey->u.be64[i]); + if (carry) { + /* Beware wrap if v == 0xFFFF.... */ + carry = (group[i] <= v); + v++; + } else + carry = (group[i] < v); + + v = group[i] - v; + seckey->u.be64[i] = cpu_to_be64(v); + } +} + +#if 0 +int main(int argc, char *argv[]) +{ + struct seckey k; + + k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); + k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); + k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); + k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364141ULL); + flip_key(&k); + assert(k.u.be64[0] == 0); + assert(k.u.be64[1] == 0); + assert(k.u.be64[2] == 0); + assert(k.u.be64[3] == 0); + flip_key(&k); + assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); + assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); + assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); + assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364141ULL)); + + k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); + k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); + k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); + k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364142ULL); + flip_key(&k); + assert(k.u.be64[0] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[1] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[2] == 0xFFFFFFFFFFFFFFFFULL); + assert(k.u.be64[3] == 0xFFFFFFFFFFFFFFFFULL); + flip_key(&k); + assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); + assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); + assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); + assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364142ULL)); + + return 0; +} +#endif + +static void random_key(secp256k1_context *ctx, + struct seckey *seckey, secp256k1_pubkey *pkey) +{ + do { + random_bytes(seckey->u.u8, sizeof(seckey->u)); + } while (!secp256k1_ec_pubkey_create(ctx, pkey, seckey->u.u8)); +} + +/* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ +static void gen_keys(secp256k1_context *ctx, + struct seckey *seckey, struct onion_pubkey *pubkey) +{ + unsigned char tmp[33]; + secp256k1_pubkey pkey; + size_t len; + + random_key(ctx, seckey, &pkey); + + secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, + SECP256K1_EC_COMPRESSED); + assert(len == sizeof(tmp)); + if (tmp[0] == 0x3) + flip_key(seckey); + memcpy(pubkey, tmp+1, sizeof(*pubkey)); +} + +int main(int argc, char *argv[]) +{ + secp256k1_context *ctx; + struct seckey seckey; + struct onion_pubkey pubkey; + char sechex[hex_str_size(sizeof(seckey))]; + char pubhex[hex_str_size(sizeof(pubkey))]; + + if (argv[1]) + srandom(atoi(argv[1])); + else + srandom(time(NULL) + getpid()); + + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + gen_keys(ctx, &seckey, &pubkey); + + hex_encode(&seckey, sizeof(seckey), sechex, sizeof(sechex)); + hex_encode(&pubkey, sizeof(pubkey), pubhex, sizeof(pubhex)); + printf("%s:%s\n", sechex, pubhex); + return 0; +} From 7c36a3e0585819cf229f7eaeddc3a1440a015c74 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:03:31 +1030 Subject: [PATCH 15/25] test_onion: get rid of dummy crypto options. Signed-off-by: Rusty Russell --- test/test_onion.c | 58 +++++++---------------------------------------- 1 file changed, 8 insertions(+), 50 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 9a2326fca..164378e7e 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -29,9 +29,6 @@ of m, for each message. */ -//#define EXPORT_FRIENDLY 1 /* No crypto! */ -//#define NO_HMAC 1 /* No real hmac */ - struct enckey { struct sha256 k; }; @@ -72,27 +69,21 @@ static struct hmackey hmackey_from_secret(const unsigned char secret[32]) } -static struct iv iv_from_secret(const unsigned char secret[32], size_t i) +static struct iv iv_from_secret(const unsigned char secret[32]) { struct iv iv; struct sha256 sha; sha_with_seed(secret, 2, &sha); memcpy(iv.iv, sha.u.u8, sizeof(iv.iv)); -#ifdef EXPORT_FRIENDLY - iv.iv[0] = i*2; -#endif return iv; } -static struct iv pad_iv_from_secret(const unsigned char secret[32], size_t i) +static struct iv pad_iv_from_secret(const unsigned char secret[32]) { struct iv iv; struct sha256 sha; sha_with_seed(secret, 3, &sha); memcpy(iv.iv, sha.u.u8, sizeof(iv.iv)); -#ifdef EXPORT_FRIENDLY - iv.iv[0] = i*2 + 1; -#endif return iv; } @@ -246,15 +237,6 @@ static struct hop *myhop(const struct onion *onion) static bool aes_encrypt(void *dst, const void *src, size_t len, const struct enckey *enckey, const struct iv *iv) { -#ifdef EXPORT_FRIENDLY - unsigned char *dptr = dst; - const unsigned char *sptr = memcheck(src, len); - size_t i; - - for (i = 0; i < len; i++) - dptr[i] = sptr[i] + iv->iv[0] + i / sizeof(struct hop); - return true; -#else EVP_CIPHER_CTX evpctx; int outlen; @@ -275,21 +257,11 @@ static bool aes_encrypt(void *dst, const void *src, size_t len, return false; assert(outlen == 0); return true; -#endif } static bool aes_decrypt(void *dst, const void *src, size_t len, const struct enckey *enckey, const struct iv *iv) { -#ifdef EXPORT_FRIENDLY - unsigned char *dptr = dst; - const unsigned char *sptr = memcheck(src, len); - size_t i; - - for (i = 0; i < len; i++) - dptr[i] = sptr[i] - iv->iv[0] - i / sizeof(struct hop); - return true; -#else EVP_CIPHER_CTX evpctx; int outlen; @@ -310,7 +282,6 @@ static bool aes_decrypt(void *dst, const void *src, size_t len, return false; assert(outlen == 0); return true; -#endif } void dump_contents(const void *data, size_t n) @@ -363,18 +334,6 @@ static void make_hmac(const struct hop *hops, size_t num_hops, const struct hmackey *hmackey, struct sha256 *hmac) { -#ifdef NO_HMAC - /* Copy first byte of message on each hop. */ - size_t i; - - memset(hmac, 0, sizeof(*hmac)); - for (i = 0; i < MAX_HOPS; i++) { - if (i < num_hops) - hmac->u.u8[i] = hops[i].msg[0]; - else - hmac->u.u8[i] = padding[i - num_hops].msg[0]; - } -#else HMAC_CTX ctx; size_t len, padlen; @@ -387,7 +346,6 @@ static void make_hmac(const struct hop *hops, size_t num_hops, len = num_hops*sizeof(struct hop) - sizeof(hops->hmac); HMAC_Update(&ctx, memcheck((unsigned char *)hops, len), len); HMAC_Final(&ctx, hmac->u.u8, NULL); -#endif } void _dump_hex(unsigned char *x, size_t s) { @@ -449,8 +407,8 @@ bool create_onion(const secp256k1_pubkey pubkey[], hmackeys[i] = hmackey_from_secret(memcheck(secret, 32)); enckeys[i] = enckey_from_secret(secret); - ivs[i] = iv_from_secret(secret, i); - pad_ivs[i] = pad_iv_from_secret(secret, i); + ivs[i] = iv_from_secret(secret); + pad_ivs[i] = pad_iv_from_secret(secret); } /* @@ -545,7 +503,7 @@ static bool pubkey_parse(const secp256k1_context *ctx, * Returns enckey and pad_iv for use in unwrap. */ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, - struct enckey *enckey, struct iv *pad_iv, size_t i) + struct enckey *enckey, struct iv *pad_iv) { secp256k1_context *ctx; unsigned char secret[32]; @@ -564,8 +522,8 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, hmackey = hmackey_from_secret(secret); *enckey = enckey_from_secret(secret); - iv = iv_from_secret(secret, i); - *pad_iv = pad_iv_from_secret(secret, i); + iv = iv_from_secret(secret); + *pad_iv = pad_iv_from_secret(secret); /* Check HMAC. */ #if 0 @@ -661,7 +619,7 @@ int main(int argc, char *argv[]) printf("Decrypting with key %zi\n", i); - if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv, i)) + if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv)) errx(1, "Decrypting onion for hop %zi", i); if (strcmp((char *)myhop(&onion)->msg, msgs[i]) != 0) errx(1, "Bad message for hop %zi", i); From ed46dd355dbd8c6670069f24f73957249849733e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Oct 2015 12:03:53 +1030 Subject: [PATCH 16/25] test_onion: split encode and decode, drive from cmdline. This lets us test interaction with python code, for example. Signed-off-by: Rusty Russell --- Makefile | 4 +- test/onion_key.h | 2 + test/test_onion.c | 117 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index f95b4d557..c666091ad 100644 --- a/Makefile +++ b/Makefile @@ -100,8 +100,8 @@ test-cli-tests: $(TEST_CLI_PROGRAMS) cd test-cli; scripts/shutdown.sh 2>/dev/null || true set -e; cd test-cli; for args in "" --steal --unilateral --htlc-onchain; do scripts/setup.sh && scripts/test.sh $$args && scripts/shutdown.sh; done -test-onion: test/test_onion - set -e; for i in `seq 20`; do ./test/test_onion $$i; done +test-onion: test/test_onion test/onion_key + set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(for k in `seq 20`; do test/onion_key $$k; done | cut -d: -f2) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key $$k | cut -d: -f1) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF check: test-cli-tests test-onion diff --git a/test/onion_key.h b/test/onion_key.h index 9fd0b5a4f..17335ed5c 100644 --- a/test/onion_key.h +++ b/test/onion_key.h @@ -1,9 +1,11 @@ #ifndef ONION_KEY_H #define ONION_KEY_H #include +#include "bitcoin/privkey.h" struct seckey { union { + struct privkey k; unsigned char u8[32]; beint64_t be64[4]; } u; diff --git a/test/test_onion.c b/test/test_onion.c index 164378e7e..e6f6f6bff 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -2,6 +2,7 @@ #include "onion_key.h" #include "secp256k1.h" #include "secp256k1_ecdh.h" +#include "version.h" #include #include #include @@ -15,6 +16,9 @@ #include #include #include +#include +#include +#include /* * The client knows the server's public key S (which has corresponding @@ -581,51 +585,102 @@ bool peel_onion(struct onion *onion, sizeof(onion->hop[0]), enckey, pad_iv); } +static bool parse_onion_pubkey(secp256k1_context *ctx, + const char *arg, secp256k1_pubkey *pubkey) +{ + unsigned char tmp[33] = { 0x2 }; + + if (!hex_decode(arg, strlen(arg), tmp + 1, sizeof(tmp) - 1)) + return false; + + return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); +} + +static char *make_message(const secp256k1_pubkey *pubkey) +{ + char *m; + char hexstr[hex_str_size(20)]; + + hex_encode(pubkey, 20, hexstr, sizeof(hexstr)); + asprintf(&m, "Message for %s...", hexstr); + return m; +} + int main(int argc, char *argv[]) { secp256k1_context *ctx; - size_t i, hops; - struct seckey seckeys[MAX_HOPS]; - secp256k1_pubkey pubkeys[MAX_HOPS]; - char *msgs[MAX_HOPS]; struct onion onion; + bool generate = false, decode = false; assert(EVP_CIPHER_iv_length(EVP_aes_128_ctr()) == sizeof(struct iv)); - if (argc != 2) - errx(1, "Usage: %s ", argv[0]); - hops = atoi(argv[1]); - if (hops == 0 || hops > MAX_HOPS) - errx(1, "%s is invalid number of hops", argv[1]); - - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - for (i = 0; i < hops; i++) { - asprintf(&msgs[i], "Message to %zu", i); - random_key(ctx, &seckeys[i], &pubkeys[i]); - printf(" * Keypair %zu:", i); - dump_hex(seckeys[i]); - dump_pkey(ctx, pubkeys[i]); - printf("\n"); - } + opt_register_noarg("--help|-h", opt_usage_and_exit, + "--generate ... OR\n" + "--decode \n" + "Either create an onion message, or decode one step", + "Print this message."); + opt_register_noarg("--generate", + opt_set_bool, &generate, + "Generate onion through the given hex pubkeys"); + opt_register_noarg("--decode", + opt_set_bool, &decode, + "Decode onion given the private key"); + opt_register_version(); + + opt_parse(&argc, argv, opt_log_stderr_exit); - if (!create_onion(pubkeys, msgs, hops, &onion)) - errx(1, "Creating onion packet failed"); - printf(" * Message:"); dump_hex(onion); printf("\n"); + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + if (generate) { + secp256k1_pubkey pubkeys[MAX_HOPS]; + char *msgs[MAX_HOPS]; + size_t i; + + if (argc == 1) + opt_usage_exit_fail("Expected at least one pubkey"); + if (argc-1 > MAX_HOPS) + opt_usage_exit_fail("Expected at most %u pubkeys", + MAX_HOPS); + for (i = 1; i < argc; i++) { + if (!parse_onion_pubkey(ctx, argv[i], &pubkeys[i-1])) + errx(1, "Bad pubkey '%s'", argv[i]); + msgs[i-1] = make_message(&pubkeys[i-1]); + } - /* Now parse and peel. */ - for (i = 0; i < hops; i++) { + if (!create_onion(pubkeys, msgs, argc - 1, &onion)) + errx(1, "Creating onion packet failed"); + if (!write_all(STDOUT_FILENO, &onion, sizeof(onion))) + err(1, "Writing onion packet"); + return 0; + } else if (decode) { + struct seckey seckey; + secp256k1_pubkey pubkey; struct enckey enckey; struct iv pad_iv; - printf("Decrypting with key %zi\n", i); + if (argc != 2) + opt_usage_exit_fail("Expect a privkey with --decode"); + + if (!hex_decode(argv[1], strlen(argv[1]), &seckey, sizeof(seckey))) + errx(1, "Invalid private key hex '%s'", argv[1]); + if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey.u.u8)) + errx(1, "Invalid private key '%s'", argv[1]); - if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv)) - errx(1, "Decrypting onion for hop %zi", i); - if (strcmp((char *)myhop(&onion)->msg, msgs[i]) != 0) - errx(1, "Bad message for hop %zi", i); + if (!read_all(STDIN_FILENO, &onion, sizeof(onion))) + errx(1, "Reading in onion"); + + if (!decrypt_onion(&seckey, &onion, &enckey, &pad_iv)) + errx(1, "Failed decrypting onion for '%s'", argv[1]); + if (strncmp((char *)myhop(&onion)->msg, make_message(&pubkey), + sizeof(myhop(&onion)->msg))) + errx(1, "Bad message '%s'", (char *)myhop(&onion)->msg); if (!peel_onion(&onion, &enckey, &pad_iv)) - errx(1, "Peeling onion for hop %zi", i); - } + errx(1, "Peeling onion for '%s'", argv[1]); + if (!write_all(STDOUT_FILENO, &onion, sizeof(onion))) + err(1, "Writing onion packet"); + return 0; + } else + opt_usage_exit_fail("Need --decode or --generate"); + secp256k1_context_destroy(ctx); return 0; } From 2042e1cdb746b58b6641211ff96963ad92174889 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 23:49:52 +1000 Subject: [PATCH 17/25] onion_key: generate multiple keys at once --- Makefile | 2 +- test/onion_key.c | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c666091ad..5d229915e 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ test-cli-tests: $(TEST_CLI_PROGRAMS) set -e; cd test-cli; for args in "" --steal --unilateral --htlc-onchain; do scripts/setup.sh && scripts/test.sh $$args && scripts/shutdown.sh; done test-onion: test/test_onion test/onion_key - set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(for k in `seq 20`; do test/onion_key $$k; done | cut -d: -f2) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key $$k | cut -d: -f1) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF + set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF check: test-cli-tests test-onion diff --git a/test/onion_key.c b/test/onion_key.c index c6ea69c57..9ab4740a7 100644 --- a/test/onion_key.c +++ b/test/onion_key.c @@ -113,7 +113,7 @@ static void gen_keys(secp256k1_context *ctx, memcpy(pubkey, tmp+1, sizeof(*pubkey)); } -int main(int argc, char *argv[]) +void print_keypair(int pub, int priv) { secp256k1_context *ctx; struct seckey seckey; @@ -121,16 +121,45 @@ int main(int argc, char *argv[]) char sechex[hex_str_size(sizeof(seckey))]; char pubhex[hex_str_size(sizeof(pubkey))]; - if (argv[1]) - srandom(atoi(argv[1])); - else - srandom(time(NULL) + getpid()); + assert(pub || priv); ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); gen_keys(ctx, &seckey, &pubkey); hex_encode(&seckey, sizeof(seckey), sechex, sizeof(sechex)); hex_encode(&pubkey, sizeof(pubkey), pubhex, sizeof(pubhex)); - printf("%s:%s\n", sechex, pubhex); + + if (pub && priv) { + printf("%s:%s\n", sechex, pubhex); + } else { + printf("%s\n", (priv ? sechex : pubhex)); + } +} + +int main(int argc, char *argv[]) +{ + int pub = 1, priv = 1; + + if (argc >= 1) { + if (strcmp(argv[1], "--pub") == 0) { + pub = 1; priv = 0; + argc--; argv++; + } else if (strcmp(argv[1], "--priv") == 0) { + pub = 0; priv = 1; + argc--; argv++; + } + } + + if (argc <= 1) { + srandom(time(NULL) + getpid()); + print_keypair(pub, priv); + } else { + int i; + for (i = 1; i < argc; i++) { + srandom(atoi(argv[i])); + print_keypair(pub, priv); + } + } + return 0; } From 9ffac49c6f91329487b93534e9f444f97c6a7a2c Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 23:49:52 +1000 Subject: [PATCH 18/25] onion_key: allowing both odd and even pubkeys output compressed public keys; accept compressed pubkey in test_onion --- test/onion_key.c | 10 +++++++--- test/onion_key.h | 5 +++++ test/test_onion.c | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/test/onion_key.c b/test/onion_key.c index 9ab4740a7..810e8fcee 100644 --- a/test/onion_key.c +++ b/test/onion_key.c @@ -20,6 +20,7 @@ static void random_bytes(void *dst, size_t n) d[i] = random() % 256; } +#if 0 /* Compressed key would start with 0x3? Subtract from group. Thanks * Greg Maxwell. */ static void flip_key(struct seckey *seckey) @@ -47,6 +48,7 @@ static void flip_key(struct seckey *seckey) seckey->u.be64[i] = cpu_to_be64(v); } } +#endif #if 0 int main(int argc, char *argv[]) @@ -97,7 +99,7 @@ static void random_key(secp256k1_context *ctx, /* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ static void gen_keys(secp256k1_context *ctx, - struct seckey *seckey, struct onion_pubkey *pubkey) + struct seckey *seckey, struct compressed_pubkey *pubkey) { unsigned char tmp[33]; secp256k1_pubkey pkey; @@ -108,16 +110,18 @@ static void gen_keys(secp256k1_context *ctx, secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, SECP256K1_EC_COMPRESSED); assert(len == sizeof(tmp)); +#if 0 if (tmp[0] == 0x3) flip_key(seckey); - memcpy(pubkey, tmp+1, sizeof(*pubkey)); +#endif + memcpy(pubkey, tmp, sizeof(*pubkey)); } void print_keypair(int pub, int priv) { secp256k1_context *ctx; struct seckey seckey; - struct onion_pubkey pubkey; + struct compressed_pubkey pubkey; char sechex[hex_str_size(sizeof(seckey))]; char pubhex[hex_str_size(sizeof(pubkey))]; diff --git a/test/onion_key.h b/test/onion_key.h index 17335ed5c..61cd36083 100644 --- a/test/onion_key.h +++ b/test/onion_key.h @@ -11,6 +11,11 @@ struct seckey { } u; }; +/* First byte is 0x02 or 0x03 indicating even or odd y */ +struct compressed_pubkey { + unsigned char u8[33]; +}; + /* Prepend 0x02 to get pubkey for libsecp256k1 */ struct onion_pubkey { unsigned char u8[32]; diff --git a/test/test_onion.c b/test/test_onion.c index e6f6f6bff..3c5df9110 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -588,9 +588,9 @@ bool peel_onion(struct onion *onion, static bool parse_onion_pubkey(secp256k1_context *ctx, const char *arg, secp256k1_pubkey *pubkey) { - unsigned char tmp[33] = { 0x2 }; + unsigned char tmp[33] = { }; - if (!hex_decode(arg, strlen(arg), tmp + 1, sizeof(tmp) - 1)) + if (!hex_decode(arg, strlen(arg), tmp, sizeof(tmp))) return false; return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); From beafbe1c19d386d7e570b4a1e81d0de2f7e3b4b3 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 23:49:52 +1000 Subject: [PATCH 19/25] test_onion.c: generate message predictably Generate sample encrypted payload based on actual pubkey, not libsecp256k1's internal representation of the pubkey. --- test/test_onion.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 3c5df9110..fa643626d 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -596,12 +596,17 @@ static bool parse_onion_pubkey(secp256k1_context *ctx, return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); } -static char *make_message(const secp256k1_pubkey *pubkey) +static char *make_message(secp256k1_context *ctx, + const secp256k1_pubkey *pubkey) { char *m; + unsigned char tmp[33]; + size_t len; char hexstr[hex_str_size(20)]; - hex_encode(pubkey, 20, hexstr, sizeof(hexstr)); + secp256k1_ec_pubkey_serialize(ctx, tmp, &len, pubkey, + SECP256K1_EC_COMPRESSED); + hex_encode(tmp+1, 20, hexstr, sizeof(hexstr)); asprintf(&m, "Message for %s...", hexstr); return m; } @@ -643,7 +648,7 @@ int main(int argc, char *argv[]) for (i = 1; i < argc; i++) { if (!parse_onion_pubkey(ctx, argv[i], &pubkeys[i-1])) errx(1, "Bad pubkey '%s'", argv[i]); - msgs[i-1] = make_message(&pubkeys[i-1]); + msgs[i-1] = make_message(ctx, &pubkeys[i-1]); } if (!create_onion(pubkeys, msgs, argc - 1, &onion)) @@ -670,7 +675,7 @@ int main(int argc, char *argv[]) if (!decrypt_onion(&seckey, &onion, &enckey, &pad_iv)) errx(1, "Failed decrypting onion for '%s'", argv[1]); - if (strncmp((char *)myhop(&onion)->msg, make_message(&pubkey), + if (strncmp((char *)myhop(&onion)->msg, make_message(ctx, &pubkey), sizeof(myhop(&onion)->msg))) errx(1, "Bad message '%s'", (char *)myhop(&onion)->msg); if (!peel_onion(&onion, &enckey, &pad_iv)) From 626be231804992a59f53e239987d34c6e076fbcc Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 23:49:52 +1000 Subject: [PATCH 20/25] test_onion.py: control generate/decode from command line --- test/test_onion.py | 102 ++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/test/test_onion.py b/test/test_onion.py index 91d818ed4..e346ea1a1 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -1,6 +1,8 @@ #!/usr/bin/env python +import argparse import sys +import time from hashlib import sha256 from binascii import hexlify, unhexlify @@ -139,6 +141,16 @@ def get_pos_y_for_x(pubkey_x, yneg=0): if pub_key_x is not None: OpenSSL.BN_free(pub_key_x) if pub_key_y is not None: OpenSSL.BN_free(pub_key_y) +def ec_decompress(pubkey, curve='secp256k1'): + if pubkey[0] == '\x02' or pubkey[0] == '\x03': + yneg = ord(pubkey[0]) & 1 + pubkey = "\x04" + pubkey[1:] + get_pos_y_for_x(pubkey[1:], yneg=yneg) + elif pubkey[0] == '\x04': + pass + else: + raise Exception("Unrecognised pubkey format: %s" % (pubkey,)) + return pubkey + class Onion(object): HMAC_LEN = 32 PKEY_LEN = 32 @@ -282,48 +294,52 @@ class OnionEncrypt(Onion): msgenc += hmac_sha256(hmacs[i], msgenc) self.onion = msgenc -def decode_from_file(f): - keys = [] - msg = "" - for ln in f.readlines(): - if ln.startswith(" * Keypair "): - w = ln.strip().split() - idx = int(w[2].strip(":")) - priv = unhexlify(w[3]) - pub = unhexlify(w[4]) - assert idx == len(keys) - keys.append(ecc.ECC(privkey=priv, pubkey=pub, curve='secp256k1')) - elif ln.startswith(" * Message:"): - msg = unhexlify(ln[11:].strip()) - elif ln.startswith("Decrypting"): - pass - else: - print ln - assert ln.strip() == "" - - assert msg != "" - for k in keys: - o = OnionDecrypt(msg, k) - o.decrypt() - print o.msg - msg = o.fwd - print "done" +def generate(args): + server_keys = [] + msgs = [] + for k in args.pubkeys: + k = unhexlify(k) + msgs.append("Message for %s..." % (hexlify(k[1:21]),)) + k = ec_decompress(k) + server_keys.append(k) + o = OnionEncrypt(msgs, server_keys) + sys.stdout.write(o.onion) + return + +def decode(args): + msg = sys.stdin.read() + key = ecc.ECC(privkey=unhexlify(args.seckey), + pubkey=ec_decompress(unhexlify(args.pubkey)), + curve='secp256k1') + o = OnionDecrypt(msg, key) + o.decrypt() + #sys.stderr.write("Message: \"%s\"\n" % (o.msg,)) + want_msg = "Message for %s..." % (args.pubkey[2:42]) + if o.msg != want_msg + "\0"*(Onion.MSG_LEN - len(want_msg)): + raise Exception("Unexpected message: \"%s\" (wanted: %s)" % (o.msg, want_msg)) + + sys.stdout.write(o.fwd) + +def main(argv): + parser = argparse.ArgumentParser(description="Process some integers.") + sp = parser.add_subparsers() + p = sp.add_parser("generate") + p.add_argument("pubkeys", nargs='+', help="public keys of recipients") + p.set_defaults(func=generate) + + p = sp.add_parser("decode") + p.add_argument("seckey", help="secret key for router") + p.add_argument("pubkey", help="public key for router") + p.set_defaults(func=decode) + + args = parser.parse_args(argv) + + return args.func(args) + + + if __name__ == "__main__": - if len(sys.argv) > 1 and sys.argv[1] == "generate": - if len(sys.argv) == 3: - n = int(sys.argv[2]) - else: - n = 20 - servers = [ecc.ECC(curve='secp256k1') for _ in range(n)] - server_pubs = [s.get_pubkey() for s in servers] - msgs = ["Howzit %d..." % (i,) for i in range(n)] - - o = OnionEncrypt(msgs, server_pubs) - - for i, s in enumerate(servers): - print " * Keypair %d: %s %s" % ( - i, hexlify(s.privkey), hexlify(s.get_pubkey())) - print " * Message: %s" % (hexlify(o.onion)) - else: - decode_from_file(sys.stdin) + main(sys.argv[1:]) + sys.exit(0) + From b66852f1aba972cf45128cf621a2992c45a48327 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 6 Oct 2015 23:49:52 +1000 Subject: [PATCH 21/25] Makefile: add python onion tests --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 5d229915e..0ef5fe9ac 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,15 @@ test-cli-tests: $(TEST_CLI_PROGRAMS) test-onion: test/test_onion test/onion_key set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF +test-onion2: test/test_onion test/onion_key + set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF + +test-onion3: test/test_onion test/onion_key + set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF + +test-onion4: test/test_onion test/onion_key + set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF + check: test-cli-tests test-onion full-check: check $(TEST_PROGRAMS) From 064cf6cc3935c3c4f309ca5e132242b8852a2a6a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Oct 2015 12:04:22 +1030 Subject: [PATCH 22/25] test/onion_key: code cleanup. Use ccan/opt, make arguments bool, remove commented-out code. Signed-off-by: Rusty Russell --- test/onion_key.c | 115 ++++++++++------------------------------------- 1 file changed, 24 insertions(+), 91 deletions(-) diff --git a/test/onion_key.c b/test/onion_key.c index 810e8fcee..06f1fe7cf 100644 --- a/test/onion_key.c +++ b/test/onion_key.c @@ -2,8 +2,10 @@ #include "secp256k1.h" #include "secp256k1_ecdh.h" #include "onion_key.h" +#include "version.h" #include #include +#include #include #include #include @@ -20,75 +22,6 @@ static void random_bytes(void *dst, size_t n) d[i] = random() % 256; } -#if 0 -/* Compressed key would start with 0x3? Subtract from group. Thanks - * Greg Maxwell. */ -static void flip_key(struct seckey *seckey) -{ - int i; - bool carry = 0; - - const int64_t group[] = { - 0xFFFFFFFFFFFFFFFFULL, - 0xFFFFFFFFFFFFFFFEULL, - 0xBAAEDCE6AF48A03BULL, - 0xBFD25E8CD0364141ULL - }; - - for (i = 3; i >= 0; i--) { - uint64_t v = be64_to_cpu(seckey->u.be64[i]); - if (carry) { - /* Beware wrap if v == 0xFFFF.... */ - carry = (group[i] <= v); - v++; - } else - carry = (group[i] < v); - - v = group[i] - v; - seckey->u.be64[i] = cpu_to_be64(v); - } -} -#endif - -#if 0 -int main(int argc, char *argv[]) -{ - struct seckey k; - - k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); - k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); - k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); - k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364141ULL); - flip_key(&k); - assert(k.u.be64[0] == 0); - assert(k.u.be64[1] == 0); - assert(k.u.be64[2] == 0); - assert(k.u.be64[3] == 0); - flip_key(&k); - assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); - assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); - assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); - assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364141ULL)); - - k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); - k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); - k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); - k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364142ULL); - flip_key(&k); - assert(k.u.be64[0] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[1] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[2] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[3] == 0xFFFFFFFFFFFFFFFFULL); - flip_key(&k); - assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); - assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); - assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); - assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364142ULL)); - - return 0; -} -#endif - static void random_key(secp256k1_context *ctx, struct seckey *seckey, secp256k1_pubkey *pkey) { @@ -101,23 +34,17 @@ static void random_key(secp256k1_context *ctx, static void gen_keys(secp256k1_context *ctx, struct seckey *seckey, struct compressed_pubkey *pubkey) { - unsigned char tmp[33]; secp256k1_pubkey pkey; size_t len; random_key(ctx, seckey, &pkey); - secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, + secp256k1_ec_pubkey_serialize(ctx, pubkey->u8, &len, &pkey, SECP256K1_EC_COMPRESSED); - assert(len == sizeof(tmp)); -#if 0 - if (tmp[0] == 0x3) - flip_key(seckey); -#endif - memcpy(pubkey, tmp, sizeof(*pubkey)); + assert(len == sizeof(pubkey->u8)); } -void print_keypair(int pub, int priv) +void print_keypair(bool pub, bool priv) { secp256k1_context *ctx; struct seckey seckey; @@ -142,19 +69,25 @@ void print_keypair(int pub, int priv) int main(int argc, char *argv[]) { - int pub = 1, priv = 1; - - if (argc >= 1) { - if (strcmp(argv[1], "--pub") == 0) { - pub = 1; priv = 0; - argc--; argv++; - } else if (strcmp(argv[1], "--priv") == 0) { - pub = 0; priv = 1; - argc--; argv++; - } - } - - if (argc <= 1) { + bool pub = true, priv = true; + + opt_register_noarg("--help|-h", opt_usage_and_exit, + "[...]\n" + "Generate (deterministic if seed) secp256k1 keys", + "Print this message."); + opt_register_noarg("--pub", + opt_set_invbool, &priv, + "Generate only the public key"); + opt_register_noarg("--priv", + opt_set_invbool, &pub, + "Generate only the private key"); + opt_register_version(); + + opt_parse(&argc, argv, opt_log_stderr_exit); + if (!priv && !pub) + opt_usage_exit_fail("Can't use --pub and --priv"); + + if (argc == 1) { srandom(time(NULL) + getpid()); print_keypair(pub, priv); } else { From 0c4eb06e26dd781ba1717a85746598ecd3017c1a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Oct 2015 12:34:45 +1030 Subject: [PATCH 23/25] test_onion: remove gratuitous dynamic alloc, cleanup on exit. We skipped freeing the context in the too-many-hops case. Signed-off-by: Rusty Russell --- test/test_onion.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index fa643626d..21b9aafe4 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -381,23 +381,23 @@ bool create_onion(const secp256k1_pubkey pubkey[], struct onion *onion) { int i; - struct seckey *seckeys = tal_arr(NULL, struct seckey, num); - struct onion_pubkey *pubkeys = tal_arr(seckeys, struct onion_pubkey, num); - struct enckey *enckeys = tal_arr(seckeys, struct enckey, num); - struct hmackey *hmackeys = tal_arr(seckeys, struct hmackey, num); - struct iv *ivs = tal_arr(seckeys, struct iv, num); - struct iv *pad_ivs = tal_arr(seckeys, struct iv, num); - HMAC_CTX *padding_hmac = tal_arr(seckeys, HMAC_CTX, num); - struct hop *padding = tal_arr(seckeys, struct hop, num); + struct seckey seckeys[MAX_HOPS]; + struct onion_pubkey pubkeys[MAX_HOPS]; + struct enckey enckeys[MAX_HOPS]; + struct hmackey hmackeys[MAX_HOPS]; + struct iv ivs[MAX_HOPS]; + struct iv pad_ivs[MAX_HOPS]; + HMAC_CTX padding_hmac[MAX_HOPS]; + struct hop padding[MAX_HOPS]; size_t junk_hops; - secp256k1_context *ctx; + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); bool ok = false; if (num > MAX_HOPS) goto fail; - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - + /* FIXME: I think it would be safe to reuse a single disposable key + * here? */ /* First generate all the keys. */ for (i = 0; i < num; i++) { unsigned char secret[32]; @@ -485,7 +485,6 @@ bool create_onion(const secp256k1_pubkey pubkey[], ok = true; fail: - tal_free(seckeys); secp256k1_context_destroy(ctx); return ok; } From beb702054b42a2ecccf10445da9d174d35222d0a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Oct 2015 13:08:04 +1030 Subject: [PATCH 24/25] test_onion: minor protocol change; use single SHA to create both IVs. Suggested-by: Anthony Towns Signed-off-by: Rusty Russell --- test/test_onion.c | 25 ++++++++----------------- test/test_onion.py | 2 +- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/test/test_onion.c b/test/test_onion.c index 21b9aafe4..7a44173b9 100644 --- a/test/test_onion.c +++ b/test/test_onion.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -73,22 +74,14 @@ static struct hmackey hmackey_from_secret(const unsigned char secret[32]) } -static struct iv iv_from_secret(const unsigned char secret[32]) +static void ivs_from_secret(const unsigned char secret[32], + struct iv *iv, struct iv *pad_iv) { - struct iv iv; struct sha256 sha; sha_with_seed(secret, 2, &sha); - memcpy(iv.iv, sha.u.u8, sizeof(iv.iv)); - return iv; -} - -static struct iv pad_iv_from_secret(const unsigned char secret[32]) -{ - struct iv iv; - struct sha256 sha; - sha_with_seed(secret, 3, &sha); - memcpy(iv.iv, sha.u.u8, sizeof(iv.iv)); - return iv; + BUILD_ASSERT(sizeof(*iv) + sizeof(*pad_iv) == sizeof(sha)); + memcpy(iv->iv, sha.u.u8, sizeof(iv->iv)); + memcpy(pad_iv->iv, sha.u.u8 + sizeof(iv->iv), sizeof(pad_iv->iv)); } /* Not really! */ @@ -411,8 +404,7 @@ bool create_onion(const secp256k1_pubkey pubkey[], hmackeys[i] = hmackey_from_secret(memcheck(secret, 32)); enckeys[i] = enckey_from_secret(secret); - ivs[i] = iv_from_secret(secret); - pad_ivs[i] = pad_iv_from_secret(secret); + ivs_from_secret(secret, &ivs[i], &pad_ivs[i]); } /* @@ -525,8 +517,7 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, hmackey = hmackey_from_secret(secret); *enckey = enckey_from_secret(secret); - iv = iv_from_secret(secret); - *pad_iv = pad_iv_from_secret(secret); + ivs_from_secret(secret, &iv, pad_iv); /* Check HMAC. */ #if 0 diff --git a/test/test_onion.py b/test/test_onion.py index e346ea1a1..c631f5e08 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -175,7 +175,7 @@ class Onion(object): enckey = cls.tweak_sha(sec, b'\x00')[:16] hmac = cls.tweak_sha(sec, b'\x01') iv = cls.tweak_sha(sec, b'\x02')[:16] - pad_iv = cls.tweak_sha(sec, b'\x03')[:16] + pad_iv = cls.tweak_sha(sec, b'\x02')[16:] return enckey, hmac, iv, pad_iv From cadaa348e367369b567d3be535b37a9f164cc297 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Wed, 7 Oct 2015 13:22:44 +1000 Subject: [PATCH 25/25] test_onion.py: drop repeated sha calculation --- test/test_onion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_onion.py b/test/test_onion.py index c631f5e08..a09983fac 100644 --- a/test/test_onion.py +++ b/test/test_onion.py @@ -174,8 +174,8 @@ class Onion(object): enckey = cls.tweak_sha(sec, b'\x00')[:16] hmac = cls.tweak_sha(sec, b'\x01') - iv = cls.tweak_sha(sec, b'\x02')[:16] - pad_iv = cls.tweak_sha(sec, b'\x02')[16:] + ivs = cls.tweak_sha(sec, b'\x02') + iv, pad_iv = ivs[:16], ivs[16:] return enckey, hmac, iv, pad_iv