From 7bb5c279a8430886a2965e2cb152c02856ce13b5 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 23 Jun 2016 10:15:43 +0930 Subject: [PATCH 1/3] sphinx: Implemented sphinx onion routing Implements a spec-compliant sphinx onion routing format. The format has been cross-checked with the go implementation cdecker/lightning-onion@b9e117e. --- Makefile | 4 +- daemon/sphinx.c | 526 +++++++++++++++++++++++++++++++++++++++++++++ daemon/sphinx.h | 126 +++++++++++ test/test_sphinx.c | 106 +++++++++ 4 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 daemon/sphinx.c create mode 100644 daemon/sphinx.h create mode 100644 test/test_sphinx.c diff --git a/Makefile b/Makefile index 58d77afdf..54a360254 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,8 @@ FEATURES := $(BITCOIN_FEATURES) TEST_PROGRAMS := \ test/onion_key \ test/test_protocol \ - test/test_onion + test/test_onion \ + test/test_sphinx BITCOIN_SRC := \ bitcoin/base58.c \ @@ -38,6 +39,7 @@ BITCOIN_SRC := \ bitcoin/signature.c \ bitcoin/tx.c \ bitcoin/varint.c + BITCOIN_OBJS := $(BITCOIN_SRC:.c=.o) CORE_SRC := \ diff --git a/daemon/sphinx.c b/daemon/sphinx.c new file mode 100644 index 000000000..069f66314 --- /dev/null +++ b/daemon/sphinx.c @@ -0,0 +1,526 @@ +#include "sphinx.h" +#include + +#include +#include +#include + +#include + +#include +#include + +#define BLINDING_FACTOR_SIZE 32 +#define SHARED_SECRET_SIZE 32 +#define NUM_STREAM_BYTES (2 * NUM_MAX_HOPS + 2) * SECURITY_PARAMETER +#define KEY_LEN 32 + +struct hop_params { + u8 secret[SHARED_SECRET_SIZE]; + u8 blind[BLINDING_FACTOR_SIZE]; + secp256k1_pubkey ephemeralkey; +}; + +struct keyset { + u8 pi[KEY_LEN]; + u8 mu[KEY_LEN]; + u8 rho[KEY_LEN]; + u8 gamma[KEY_LEN]; +}; + +/* Small helper to append data to a buffer and update the position + * into the buffer + */ +static void write_buffer(u8 *dst, const void *src, const size_t len, int *pos) +{ + memcpy(dst + *pos, src, len); + *pos += len; +} + +/* Read len bytes from the source at position pos into dst and update + * the position pos accordingly. + */ +static void read_buffer(void *dst, const u8 *src, const size_t len, int *pos) +{ + memcpy(dst, src + *pos, len); + *pos += len; +} + +u8 *serialize_onionpacket( + const tal_t *ctx, + const secp256k1_context *secpctx, + const struct onionpacket *m) +{ + u8 *dst = tal_arr(ctx, u8, TOTAL_PACKET_SIZE); + + u8 der[33]; + size_t outputlen = 33; + int p = 0; + + secp256k1_ec_pubkey_serialize(secpctx, + der, + &outputlen, + &m->ephemeralkey, + SECP256K1_EC_COMPRESSED); + + write_buffer(dst, &m->version, 1, &p); + write_buffer(dst, der, outputlen, &p); + write_buffer(dst, m->mac, sizeof(m->mac), &p); + write_buffer(dst, m->routinginfo, ROUTING_INFO_SIZE, &p); + write_buffer(dst, m->hoppayloads, TOTAL_HOP_PAYLOAD_SIZE, &p); + write_buffer(dst, m->payload, MESSAGE_SIZE, &p); + return dst; +} + +struct onionpacket *parse_onionpacket( + const tal_t *ctx, + const secp256k1_context *secpctx, + const void *src, + const size_t srclen + ) +{ + struct onionpacket *m; + int p = 0; + u8 rawEphemeralkey[33]; + + if (srclen != TOTAL_PACKET_SIZE) + return NULL; + + m = talz(ctx, struct onionpacket); + + read_buffer(&m->version, src, 1, &p); + if (m->version != 0x01) { + // FIXME add logging + return NULL; + } + read_buffer(rawEphemeralkey, src, 33, &p); + + if (secp256k1_ec_pubkey_parse(secpctx, &m->ephemeralkey, rawEphemeralkey, 33) != 1) + return NULL; + + read_buffer(&m->mac, src, 20, &p); + read_buffer(&m->routinginfo, src, ROUTING_INFO_SIZE, &p); + read_buffer(&m->hoppayloads, src, TOTAL_HOP_PAYLOAD_SIZE, &p); + read_buffer(m->payload, src, MESSAGE_SIZE, &p); + return m; +} + +static struct hoppayload *parse_hoppayload(const tal_t *ctx, u8 *src) +{ + int p = 0; + struct hoppayload *result = talz(ctx, struct hoppayload); + + read_buffer(&result->realm, src, sizeof(&result->realm), &p); + read_buffer(&result->amount, src, sizeof(&result->amount), &p); + read_buffer(&result->remainder, src, sizeof(&result->remainder), &p); + return result; +} + +static void serialize_hoppayload(u8 *dst, struct hoppayload *hp) +{ + int p = 0; + + write_buffer(dst, &hp->realm, sizeof(&hp->realm), &p); + write_buffer(dst, &hp->amount, sizeof(&hp->amount), &p); + write_buffer(dst, &hp->remainder, sizeof(&hp->remainder), &p); +} + + +static void xorbytes(uint8_t *d, const uint8_t *a, const uint8_t *b, size_t len) +{ + size_t i = 0; + + for (i = 0; i < len; i++) + d[i] = a[i] ^ b[i]; +} + +/* + * Encrypt a message `m` of length `mlen` with key `key` and store the + * ciphertext in `c`. `c` must be pre-allocated to at least `mlen` bytes. + */ +static void stream_encrypt(void *c, const void *m, const size_t mlen, const u8 *key) +{ + u8 nonce[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + memcheck(c, mlen); + crypto_stream_chacha20_xor(c, m, mlen, nonce, key); +} + +/* + * Decrypt a ciphertext `c` of length `clen` with key `key` and store the + * cleartext in `m`. `m` must be pre-allocated to at least `clen` bytes. + */ +static void stream_decrypt(void *m, const void *c, const size_t clen, const u8 *key) +{ + stream_encrypt(m, c, clen, key); +} + +/* + * Generate a pseudo-random byte stream of length `dstlen` from key `k` and + * store it in `dst`. `dst must be at least `dstlen` bytes long. + */ +static void generate_cipher_stream(void *dst, const u8 *k, size_t dstlen) +{ + u8 nonce[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + crypto_stream_chacha20(dst, dstlen, nonce, k); +} + +static bool compute_hmac( + void *dst, + const void *src, + size_t len, + const void *key, + size_t keylen) +{ + crypto_auth_hmacsha256_state state; + + crypto_auth_hmacsha256_init(&state, key, keylen); + crypto_auth_hmacsha256_update(&state, memcheck(src, len), len); + crypto_auth_hmacsha256_final(&state, dst); + return true; +} + +static void compute_packet_hmac(struct onionpacket *packet, u8 *mukey, u8 *hmac) +{ + u8 mactemp[ROUTING_INFO_SIZE + TOTAL_HOP_PAYLOAD_SIZE + MESSAGE_SIZE]; + + memcpy(mactemp, packet->routinginfo, ROUTING_INFO_SIZE); + memcpy(mactemp + ROUTING_INFO_SIZE, packet->hoppayloads, TOTAL_HOP_PAYLOAD_SIZE); + memcpy(mactemp + ROUTING_INFO_SIZE + TOTAL_HOP_PAYLOAD_SIZE, packet->payload, sizeof(packet->payload)); + compute_hmac(hmac, mactemp, sizeof(mactemp), mukey, KEY_LEN); +} + +static bool generate_key(void *k, const char *t, u8 tlen, const u8 *s) +{ + return compute_hmac(k, s, KEY_LEN, t, tlen); +} + +static bool generate_header_padding( + void *dst, size_t dstlen, + const size_t hopsize, + const char *keytype, + size_t keytypelen, + const u8 numhops, + struct hop_params *params + ) +{ + int i; + u8 cipher_stream[(NUM_MAX_HOPS + 1) * hopsize]; + u8 key[KEY_LEN]; + + memset(dst, 0, dstlen); + for (i = 1; i < numhops; i++) { + if (!generate_key(&key, keytype, keytypelen, params[i - 1].secret)) + return false; + + generate_cipher_stream(cipher_stream, key, sizeof(cipher_stream)); + int pos = ((NUM_MAX_HOPS - i) + 1) * hopsize; + xorbytes(dst, dst, cipher_stream + pos, sizeof(cipher_stream) - pos); + } + return true; +} + +static void compute_blinding_factor(secp256k1_context *secpctx, + secp256k1_pubkey *key, + u8 sharedsecret[SHARED_SECRET_SIZE], + u8 res[BLINDING_FACTOR_SIZE]) +{ + struct sha256_ctx ctx; + u8 der[33]; + size_t outputlen = 33; + struct sha256 temp; + + secp256k1_ec_pubkey_serialize(secpctx, der, &outputlen, key, + SECP256K1_EC_COMPRESSED); + sha256_init(&ctx); + sha256_update(&ctx, der, sizeof(der)); + sha256_update(&ctx, sharedsecret, SHARED_SECRET_SIZE); + sha256_done(&ctx, &temp); + memcpy(res, &temp, 32); +} + +static bool blind_group_element( + secp256k1_context *secpctx, + secp256k1_pubkey *blindedelement, + secp256k1_pubkey *pubkey, + u8 blind[BLINDING_FACTOR_SIZE]) +{ + /* tweak_mul is inplace so copy first. */ + if (pubkey != blindedelement) + memcpy(blindedelement, pubkey, sizeof(secp256k1_pubkey)); + if (secp256k1_ec_pubkey_tweak_mul(secpctx, blindedelement, blind) != 1) + return false; + return true; +} + +static bool create_shared_secret( + secp256k1_context *secpctx, + u8 *secret, + const secp256k1_pubkey *pubkey, + const u8 *sessionkey) +{ + /* Need to copy since tweak is in-place */ + secp256k1_pubkey pkcopy; + u8 ecres[33]; + + memcpy(&pkcopy, pubkey, sizeof(pkcopy)); + + if (secp256k1_ec_pubkey_tweak_mul(secpctx, &pkcopy, sessionkey) != 1) + return false; + + /* Serialize and strip first byte, this gives us the X coordinate */ + size_t outputlen = 33; + secp256k1_ec_pubkey_serialize(secpctx, ecres, &outputlen, + &pkcopy, SECP256K1_EC_COMPRESSED); + struct sha256 h; + sha256(&h, ecres + 1, sizeof(ecres) - 1); + memcpy(secret, &h, sizeof(h)); + return true; +} + +void pubkey_hash160( + const secp256k1_context *secpctx, + u8 *dst, + const struct pubkey *pubkey) +{ + struct ripemd160 r; + struct sha256 h; + u8 der[33]; + size_t outputlen = 33; + + secp256k1_ec_pubkey_serialize(secpctx, + der, + &outputlen, + &pubkey->pubkey, + SECP256K1_EC_COMPRESSED); + sha256(&h, der, sizeof(der)); + ripemd160(&r, h.u.u8, sizeof(h)); + + memcpy(dst, r.u.u8, sizeof(r)); +} + +static void generate_key_set(u8 secret[SHARED_SECRET_SIZE], struct keyset *keys) +{ + generate_key(keys->rho, "rho", 3, secret); + generate_key(keys->pi, "pi", 2, secret); + generate_key(keys->mu, "mu", 2, secret); + generate_key(keys->gamma, "gamma", 5, secret); +} + +static struct hop_params *generate_hop_params( + const tal_t *ctx, + secp256k1_context *secpctx, + const u8 *sessionkey, + struct pubkey path[]) +{ + int i, j, num_hops = tal_count(path); + secp256k1_pubkey temp; + u8 blind[BLINDING_FACTOR_SIZE]; + struct hop_params *params = tal_arr(ctx, struct hop_params, num_hops); + + /* Initialize the first hop with the raw information */ + if (secp256k1_ec_pubkey_create( + secpctx, ¶ms[0].ephemeralkey, sessionkey) != 1) + return NULL; + + if (!create_shared_secret( + secpctx, params[0].secret, &path[0].pubkey, sessionkey)) + return NULL; + + compute_blinding_factor( + secpctx, ¶ms[0].ephemeralkey, params[0].secret, + params[0].blind); + + /* Recursively compute all following ephemeral public keys, + * secrets and blinding factors + */ + for (i = 1; i < num_hops; i++) { + if (!blind_group_element( + secpctx, ¶ms[i].ephemeralkey, + ¶ms[i - 1].ephemeralkey, + params[i - 1].blind)) + return NULL; + + /* Blind this hop's point with all previous blinding factors + * Order is indifferent, multiplication is commutative. + */ + memcpy(&blind, sessionkey, 32); + memcpy(&temp, &path[i], sizeof(temp)); + if (!blind_group_element(secpctx, &temp, &temp, blind)) + return NULL; + for (j = 0; j < i; j++) + if (!blind_group_element( + secpctx, + &temp, + &temp, + params[j].blind)) + return NULL; + + /* Now hash temp and store it. This requires us to + * DER-serialize first and then skip the sign byte. + */ + u8 der[33]; + size_t outputlen = 33; + secp256k1_ec_pubkey_serialize( + secpctx, der, &outputlen, &temp, + SECP256K1_EC_COMPRESSED); + struct sha256 h; + sha256(&h, der + 1, sizeof(der) - 1); + memcpy(¶ms[i].secret, &h, sizeof(h)); + + compute_blinding_factor( + secpctx, ¶ms[i].ephemeralkey, + params[i].secret, params[i].blind); + } + return params; +} + +struct onionpacket *create_onionpacket( + const tal_t *ctx, + secp256k1_context *secpctx, + struct pubkey *path, + struct hoppayload hoppayloads[], + const u8 *sessionkey, + const u8 *message, + const size_t messagelen + ) +{ + struct onionpacket *packet = talz(ctx, struct onionpacket); + int i, num_hops = tal_count(path); + u8 filler[2 * (num_hops - 1) * SECURITY_PARAMETER]; + u8 hopfiller[(num_hops - 1) * HOP_PAYLOAD_SIZE]; + struct keyset keys; + u8 nextaddr[20], nexthmac[SECURITY_PARAMETER]; + u8 stream[ROUTING_INFO_SIZE], hopstream[TOTAL_HOP_PAYLOAD_SIZE]; + struct hop_params *params = generate_hop_params(ctx, secpctx, sessionkey, path); + u8 binhoppayloads[tal_count(path)][HOP_PAYLOAD_SIZE]; + + for (i = 0; i < num_hops; i++) + serialize_hoppayload(binhoppayloads[i], &hoppayloads[i]); + + if (MESSAGE_SIZE > messagelen) { + memset(&packet->hoppayloads, 0, TOTAL_HOP_PAYLOAD_SIZE); + memset(&packet->payload, 0xFF, MESSAGE_SIZE); + memcpy(&packet->payload, message, messagelen); + packet->payload[messagelen] = 0x7f; + } + + if (!params) + return NULL; + packet->version = 1; + memset(nextaddr, 0, 20); + memset(nexthmac, 0, 20); + memset(packet->routinginfo, 0, ROUTING_INFO_SIZE); + + generate_header_padding(filler, sizeof(filler), 2 * SECURITY_PARAMETER, + "rho", 3, num_hops, params); + generate_header_padding(hopfiller, sizeof(hopfiller), HOP_PAYLOAD_SIZE, + "gamma", 5, num_hops, params); + + for (i = num_hops - 1; i >= 0; i--) { + generate_key_set(params[i].secret, &keys); + generate_cipher_stream(stream, keys.rho, ROUTING_INFO_SIZE); + + /* Rightshift mix-header by 2*SECURITY_PARAMETER */ + memmove(packet->routinginfo + 2 * SECURITY_PARAMETER, packet->routinginfo, + ROUTING_INFO_SIZE - 2 * SECURITY_PARAMETER); + memcpy(packet->routinginfo, nextaddr, SECURITY_PARAMETER); + memcpy(packet->routinginfo + SECURITY_PARAMETER, nexthmac, SECURITY_PARAMETER); + xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); + + /* Rightshift hop-payloads and obfuscate */ + memmove(packet->hoppayloads + HOP_PAYLOAD_SIZE, packet->hoppayloads, + TOTAL_HOP_PAYLOAD_SIZE - HOP_PAYLOAD_SIZE); + memcpy(packet->hoppayloads, binhoppayloads[i], HOP_PAYLOAD_SIZE); + generate_cipher_stream(hopstream, keys.gamma, TOTAL_HOP_PAYLOAD_SIZE); + xorbytes(packet->hoppayloads, packet->hoppayloads, hopstream, + TOTAL_HOP_PAYLOAD_SIZE); + + if (i == num_hops - 1) { + size_t len = (NUM_MAX_HOPS - num_hops + 1) * 2 * SECURITY_PARAMETER; + memcpy(packet->routinginfo + len, filler, sizeof(filler)); + len = (NUM_MAX_HOPS - num_hops + 1) * HOP_PAYLOAD_SIZE; + memcpy(packet->hoppayloads + len, hopfiller, sizeof(hopfiller)); + } + + /* Obfuscate end-to-end payload */ + stream_encrypt(packet->payload, packet->payload, sizeof(packet->payload), keys.pi); + + compute_packet_hmac(packet, keys.mu, nexthmac); + pubkey_hash160(secpctx, nextaddr, &path[i]); + } + memcpy(packet->mac, nexthmac, sizeof(nexthmac)); + memcpy(&packet->ephemeralkey, ¶ms[0].ephemeralkey, sizeof(secp256k1_pubkey)); + return packet; +} + +/* + * Given a onionpacket msg extract the information for the current + * node and unwrap the remainder so that the node can forward it. + */ +struct route_step *process_onionpacket( + const tal_t *ctx, + secp256k1_context *secpctx, + struct onionpacket *msg, + struct privkey *hop_privkey + ) +{ + struct route_step *step = talz(ctx, struct route_step); + u8 secret[SHARED_SECRET_SIZE]; + u8 hmac[20]; + struct keyset keys; + u8 paddedhoppayloads[TOTAL_HOP_PAYLOAD_SIZE + HOP_PAYLOAD_SIZE]; + u8 hopstream[TOTAL_HOP_PAYLOAD_SIZE + HOP_PAYLOAD_SIZE]; + u8 blind[BLINDING_FACTOR_SIZE]; + u8 stream[NUM_STREAM_BYTES]; + u8 paddedheader[ROUTING_INFO_SIZE + 2 * SECURITY_PARAMETER]; + + step->next = talz(ctx, struct onionpacket); + step->next->version = msg->version; + create_shared_secret(secpctx, secret, &msg->ephemeralkey, hop_privkey->secret); + generate_key_set(secret, &keys); + + compute_packet_hmac(msg, keys.mu, hmac); + + if (memcmp(msg->mac, hmac, sizeof(hmac)) != 0) { + warnx("Computed MAC does not match expected MAC, the message was modified."); + return NULL; + } + + //FIXME:store seen secrets to avoid replay attacks + generate_cipher_stream(stream, keys.rho, sizeof(stream)); + + memset(paddedheader, 0, sizeof(paddedheader)); + memcpy(paddedheader, msg->routinginfo, ROUTING_INFO_SIZE); + xorbytes(paddedheader, paddedheader, stream, sizeof(stream)); + + /* Extract the per-hop payload */ + generate_cipher_stream(hopstream, keys.gamma, sizeof(hopstream)); + + memset(paddedhoppayloads, 0, sizeof(paddedhoppayloads)); + memcpy(paddedhoppayloads, msg->hoppayloads, TOTAL_HOP_PAYLOAD_SIZE); + xorbytes(paddedhoppayloads, paddedhoppayloads, hopstream, sizeof(hopstream)); + step->hoppayload = parse_hoppayload(step, paddedhoppayloads); + memcpy(&step->next->hoppayloads, paddedhoppayloads + HOP_PAYLOAD_SIZE, + TOTAL_HOP_PAYLOAD_SIZE); + + compute_blinding_factor(secpctx, &msg->ephemeralkey, secret, blind); + if (!blind_group_element(secpctx, &step->next->ephemeralkey, &msg->ephemeralkey, blind)) + return NULL; + memcpy(&step->next->nexthop, paddedheader, SECURITY_PARAMETER); + memcpy(&step->next->mac, + paddedheader + SECURITY_PARAMETER, + SECURITY_PARAMETER); + + stream_decrypt(step->next->payload, msg->payload, sizeof(msg->payload), keys.pi); + memcpy(&step->next->routinginfo, paddedheader + 2 * SECURITY_PARAMETER, ROUTING_INFO_SIZE); + + if (memeqzero(step->next->mac, sizeof(&step->next->mac))) { + step->nextcase = ONION_END; + } else { + step->nextcase = ONION_FORWARD; + } + + return step; +} diff --git a/daemon/sphinx.h b/daemon/sphinx.h new file mode 100644 index 000000000..0109391a9 --- /dev/null +++ b/daemon/sphinx.h @@ -0,0 +1,126 @@ +#ifndef LIGHTNING_DAEMON_SPHINX_H +#define LIGHTNING_DAEMON_SPHINX_H + +#include "config.h" +#include "bitcoin/privkey.h" +#include "bitcoin/pubkey.h" + +#include +#include +#include +#include + +#define SECURITY_PARAMETER 20 +#define NUM_MAX_HOPS 20 +#define HOP_PAYLOAD_SIZE 20 +#define TOTAL_HOP_PAYLOAD_SIZE NUM_MAX_HOPS * HOP_PAYLOAD_SIZE +#define MESSAGE_SIZE 0 +#define ROUTING_INFO_SIZE 2 * NUM_MAX_HOPS * SECURITY_PARAMETER +#define TOTAL_PACKET_SIZE 1 + 33 + SECURITY_PARAMETER + ROUTING_INFO_SIZE + \ + TOTAL_HOP_PAYLOAD_SIZE + MESSAGE_SIZE + +struct onionpacket { + /* Cleartext information */ + u8 version; + u8 nexthop[20]; + u8 mac[20]; + secp256k1_pubkey ephemeralkey; + + /* Encrypted information */ + u8 routinginfo[ROUTING_INFO_SIZE]; + u8 hoppayloads[TOTAL_HOP_PAYLOAD_SIZE]; + u8 payload[MESSAGE_SIZE]; +}; + +enum route_next_case { + ONION_END = 0, + ONION_FORWARD = 1, +}; + +struct hoppayload { + u8 realm; + u64 amount; + u8 remainder[11]; +}; + +struct route_step { + enum route_next_case nextcase; + struct onionpacket *next; + u8 *payload; + struct hoppayload *hoppayload; +}; + +/** + * create_onionpacket - Create a new onionpacket that can be routed + * over a path of intermediate nodes. + * + * @ctx: tal context to allocate from + * @secpctx: the secp256k1_context for EC operations + * @path: public keys of nodes along the path. + * @hoppayloads: payloads destined for individual hosts (limited to + * HOP_PAYLOAD_SIZE bytes) + * @num_hops: path length in nodes + * @sessionkey: 20 byte random session key to derive secrets from + * @message: end-to-end payload destined for the final recipient + * @messagelen: length of @message + */ +struct onionpacket *create_onionpacket( + const tal_t * ctx, + secp256k1_context * secpctx, + struct pubkey path[], + struct hoppayload hoppayloads[], + const u8 * sessionkey, + const u8 * message, + const size_t messagelen + ); + +/** + * process_onionpacket - process an incoming packet by stripping one + * onion layer and return the packet for the next hop. + * + * @ctx: tal context to allocate from + * @secpctx: the secp256k1_context for EC operations + * @packet: incoming packet being processed + * @hop_privkey: the processing node's private key to decrypt the packet + * @hoppayload: the per-hop payload destined for the processing node. + */ +struct route_step *process_onionpacket( + const tal_t * ctx, + secp256k1_context * secpctx, + struct onionpacket *packet, + struct privkey *hop_privkey + ); + +/** + * serialize_onionpacket - Serialize an onionpacket to a buffer. + * + * @ctx: tal context to allocate from + * @secpctx: the secp256k1_context for EC operations + * @packet: the packet to serialize + */ +u8 *serialize_onionpacket( + const tal_t *ctx, + const secp256k1_context *secpctx, + const struct onionpacket *packet); + +/** + * parese_onionpacket - Parse an onionpacket from a buffer. + * + * @ctx: tal context to allocate from + * @secpctx: the secp256k1_context for EC operations + * @src: buffer to read the packet from + * @srclen: length of the @src + */ +struct onionpacket *parse_onionpacket( + const tal_t *ctx, + const secp256k1_context *secpctx, + const void *src, + const size_t srclen + ); + +void pubkey_hash160( + const secp256k1_context *secpctx, + u8 *dst, + const struct pubkey *pubkey); + +#endif /* LIGHTNING_DAEMON_SPHINX_H */ diff --git a/test/test_sphinx.c b/test/test_sphinx.c new file mode 100644 index 000000000..5b365b1b2 --- /dev/null +++ b/test/test_sphinx.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemon/sphinx.h" +#include "daemon/sphinx.c" + +int main(int argc, char **argv) +{ + bool generate = false, decode = false; + secp256k1_context *secpctx = secp256k1_context_create( + SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + const tal_t *ctx = talz(NULL, tal_t); + + 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 from stdin given the private key"); + + opt_parse(&argc, argv, opt_log_stderr_exit); + + if (generate) { + int num_hops = argc - 1; + struct pubkey *path = tal_arr(ctx, struct pubkey, num_hops); + u8 privkeys[argc - 1][32]; + u8 sessionkey[32]; + + memset(&sessionkey, 'A', sizeof(sessionkey)); + + int i; + for (i = 0; i < num_hops; i++) { + hex_decode(argv[1 + i], 66, privkeys[i], 33); + if (secp256k1_ec_pubkey_create(secpctx, &path[i].pubkey, privkeys[i]) != 1) + return 1; + } + + struct hoppayload *hoppayloads = tal_arr(ctx, struct hoppayload, num_hops); + for (i=0; inext) + errx(1, "Error processing message."); + + u8 *ser = serialize_onionpacket(ctx, secpctx, step->next); + if (!ser) + errx(1, "Error serializing message."); + + hex_encode(ser, tal_count(ser), hextemp, sizeof(hextemp)); + printf("%s\n", hextemp); + } + secp256k1_context_destroy(secpctx); + tal_free(ctx); + return 0; +} From 1d3737055a4d7dbb6f87b0edad51f50dc116d0fe Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 6 Oct 2016 22:06:20 +0200 Subject: [PATCH 2/3] sphinx: Integrate sphinx OR with lightningd Now replaces the old cleartext onion routing with the sphinx implementation. --- daemon/Makefile | 4 +-- daemon/pay.c | 50 +++++++++++++++++++++++-------- daemon/peer.c | 78 ++++++++++++++++++++++++++++++++----------------- daemon/peer.h | 1 + 4 files changed, 93 insertions(+), 40 deletions(-) diff --git a/daemon/Makefile b/daemon/Makefile index b904dd18d..0274c7d17 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -30,7 +30,6 @@ DAEMON_SRC := \ daemon/jsonrpc.c \ daemon/lightningd.c \ daemon/netaddr.c \ - daemon/onion.c \ daemon/opt_time.c \ daemon/output_to_htlc.c \ daemon/packets.c \ @@ -38,6 +37,7 @@ DAEMON_SRC := \ daemon/peer.c \ daemon/routing.c \ daemon/secrets.c \ + daemon/sphinx.c \ daemon/timeout.c \ daemon/wallet.c \ daemon/watch.c \ @@ -79,7 +79,6 @@ DAEMON_HEADERS := \ daemon/lightningd.h \ daemon/log.h \ daemon/netaddr.h \ - daemon/onion.h \ daemon/opt_time.h \ daemon/output_to_htlc.h \ daemon/packets.h \ @@ -88,6 +87,7 @@ DAEMON_HEADERS := \ daemon/pseudorand.h \ daemon/routing.h \ daemon/secrets.h \ + daemon/sphinx.h \ daemon/timeout.h \ daemon/wallet.h \ daemon/watch.h diff --git a/daemon/pay.c b/daemon/pay.c index 46821625f..25f936354 100644 --- a/daemon/pay.c +++ b/daemon/pay.c @@ -4,13 +4,14 @@ #include "jsonrpc.h" #include "lightningd.h" #include "log.h" -#include "onion.h" #include "pay.h" #include "peer.h" #include "routing.h" +#include "sphinx.h" #include #include #include +#include /* Outstanding "pay" commands. */ struct pay_command { @@ -293,7 +294,6 @@ static void json_sendpay(struct command *cmd, const char *buffer, const jsmntok_t *params) { struct pubkey *ids; - u64 *amounts; jsmntok_t *routetok, *rhashtok; const jsmntok_t *t, *end; unsigned int delay; @@ -303,8 +303,12 @@ static void json_sendpay(struct command *cmd, struct pay_command *pc; bool replacing = false; const u8 *onion; + u8 sessionkey[32]; enum fail_error error_code; const char *err; + struct hoppayload *hoppayloads; + u64 amount, lastamount; + struct onionpacket *packet; if (!json_get_params(buffer, params, "route", &routetok, @@ -332,8 +336,8 @@ static void json_sendpay(struct command *cmd, end = json_next(routetok); n_hops = 0; - amounts = tal_arr(cmd, u64, n_hops); ids = tal_arr(cmd, struct pubkey, n_hops); + hoppayloads = tal_arr(cmd, struct hoppayload, 0); for (t = routetok + 1; t < end; t = json_next(t)) { const jsmntok_t *amttok, *idtok, *delaytok; @@ -353,12 +357,26 @@ static void json_sendpay(struct command *cmd, return; } - tal_resize(&amounts, n_hops+1); - if (!json_tok_u64(buffer, amttok, &amounts[n_hops])) { - command_fail(cmd, "route %zu invalid msatoshi", n_hops); - return; + if (n_hops == 0) { + /* What we will send */ + if (!json_tok_u64(buffer, amttok, &amount)) { + command_fail(cmd, "route %zu invalid msatoshi", n_hops); + return; + } + lastamount = amount; + } else{ + /* What that hop will forward */ + tal_resize(&hoppayloads, n_hops); + memset(&hoppayloads[n_hops-1], 0, sizeof(struct hoppayload)); + if (!json_tok_u64(buffer, amttok, &hoppayloads[n_hops-1].amount)) { + command_fail(cmd, "route %zu invalid msatoshi", n_hops); + return; + } + lastamount = hoppayloads[n_hops-1].amount; } + tal_resize(&ids, n_hops+1); + memset(&ids[n_hops], 0, sizeof(ids[n_hops])); if (!pubkey_from_hexstr(cmd->dstate->secpctx, buffer + idtok->start, idtok->end - idtok->start, @@ -374,6 +392,10 @@ static void json_sendpay(struct command *cmd, n_hops++; } + /* Add payload for final hop */ + tal_resize(&hoppayloads, n_hops); + memset(&hoppayloads[n_hops-1], 0, sizeof(struct hoppayload)); + if (n_hops == 0) { command_fail(cmd, "Empty route"); return; @@ -392,7 +414,7 @@ static void json_sendpay(struct command *cmd, size_t old_nhops = tal_count(pc->ids); log_add(cmd->dstate->base_log, "... succeeded"); /* Must match successful payment parameters. */ - if (pc->msatoshi != amounts[n_hops-1]) { + if (pc->msatoshi != lastamount) { command_fail(cmd, "already succeeded with amount %" PRIu64, pc->msatoshi); @@ -420,9 +442,13 @@ static void json_sendpay(struct command *cmd, return; } + randombytes_buf(&sessionkey, sizeof(sessionkey)); + /* Onion will carry us from first peer onwards. */ - onion = onion_create(cmd, cmd->dstate->secpctx, ids+1, amounts+1, - n_hops-1); + packet = create_onionpacket( + cmd, cmd->dstate->secpctx, ids, hoppayloads, + sessionkey, (u8*)"", 0); + onion = serialize_onionpacket(cmd, cmd->dstate->secpctx, packet); if (pc) pc->ids = tal_free(pc->ids); @@ -432,10 +458,10 @@ static void json_sendpay(struct command *cmd, pc->rhash = rhash; pc->rval = NULL; pc->ids = tal_steal(pc, ids); - pc->msatoshi = amounts[n_hops-1]; + pc->msatoshi = lastamount; /* Expiry for HTLCs is absolute. And add one to give some margin. */ - err = command_htlc_add(peer, amounts[0], + err = command_htlc_add(peer, amount, delay + get_block_height(cmd->dstate) + 1, &rhash, NULL, onion, &error_code, &pc->htlc); diff --git a/daemon/peer.c b/daemon/peer.c index e8b824543..bf71d35b3 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -13,7 +13,6 @@ #include "log.h" #include "names.h" #include "netaddr.h" -#include "onion.h" #include "output_to_htlc.h" #include "packets.h" #include "pay.h" @@ -24,6 +23,7 @@ #include "remove_dust.h" #include "routing.h" #include "secrets.h" +#include "sphinx.h" #include "state.h" #include "timeout.h" #include "utils.h" @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -199,6 +200,19 @@ struct peer *find_peer(struct lightningd_state *dstate, const struct pubkey *id) return NULL; } +struct peer *find_peer_by_pkhash(struct lightningd_state *dstate, const u8 *pkhash) +{ + struct peer *peer; + u8 addr[20]; + + list_for_each(&dstate->peers, peer, list) { + pubkey_hash160(dstate->secpctx, addr, peer->id); + if (memcmp(addr, pkhash, sizeof(addr)) == 0) + return peer; + } + return NULL; +} + void debug_dump_peers(struct lightningd_state *dstate) { struct peer *peer; @@ -438,11 +452,10 @@ static void set_htlc_fail(struct peer *peer, static void route_htlc_onwards(struct peer *peer, struct htlc *htlc, u64 msatoshi, - const BitcoinPubkey *pb_id, + const u8 *pb_id, const u8 *rest_of_route, const struct peer *only_dest) { - struct pubkey id; struct peer *next; struct htlc *newhtlc; enum fail_error error_code; @@ -454,19 +467,10 @@ static void route_htlc_onwards(struct peer *peer, log_add(peer->log, " (id %"PRIu64")", htlc->id); } - if (!proto_to_pubkey(peer->dstate->secpctx, pb_id, &id)) { - log_unusual(peer->log, - "Malformed pubkey for HTLC %"PRIu64, htlc->id); - command_htlc_set_fail(peer, htlc, BAD_REQUEST_400, - "Malformed pubkey"); - return; - } - - next = find_peer(peer->dstate, &id); + next = find_peer_by_pkhash(peer->dstate, pb_id); if (!next || !next->nc) { log_unusual(peer->log, "Can't route HTLC %"PRIu64": no %speer ", htlc->id, next ? "ready " : ""); - log_add_struct(peer->log, "%s", struct pubkey, &id); if (!peer->dstate->dev_never_routefail) command_htlc_set_fail(peer, htlc, NOT_FOUND_404, "Unknown peer"); @@ -505,9 +509,10 @@ static void route_htlc_onwards(struct peer *peer, static void their_htlc_added(struct peer *peer, struct htlc *htlc, struct peer *only_dest) { - RouteStep *step; - const u8 *rest_of_route; struct invoice *invoice; + struct privkey pk; + struct onionpacket *packet; + struct route_step *step = NULL; if (abs_locktime_is_seconds(&htlc->expiry)) { log_unusual(peer->log, "HTLC %"PRIu64" is in seconds", htlc->id); @@ -536,8 +541,13 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, return; } - step = onion_unwrap(peer, htlc->routing, tal_count(htlc->routing), - &rest_of_route); + //FIXME: dirty trick to retrieve unexported state + memcpy(&pk, peer->dstate->secret, sizeof(pk)); + packet = parse_onionpacket(peer, peer->dstate->secpctx, + htlc->routing, tal_count(htlc->routing)); + if (packet) + step = process_onionpacket(peer, peer->dstate->secpctx, packet, &pk); + if (!step) { log_unusual(peer->log, "Bad onion, failing HTLC %"PRIu64, htlc->id); @@ -546,8 +556,8 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, return; } - switch (step->next_case) { - case ROUTE_STEP__NEXT_END: + switch (step->nextcase) { + case ONION_END: if (only_dest) return; invoice = find_unpaid(peer->dstate, &htlc->rhash); @@ -584,19 +594,20 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, command_htlc_fulfill(peer, htlc); goto free_rest; - case ROUTE_STEP__NEXT_BITCOIN: - route_htlc_onwards(peer, htlc, step->amount, step->bitcoin, - rest_of_route, only_dest); + case ONION_FORWARD: + printf("FORWARDING %lu\n", step->hoppayload->amount); + route_htlc_onwards(peer, htlc, step->hoppayload->amount, step->next->nexthop, + serialize_onionpacket(step, peer->dstate->secpctx, step->next), only_dest); goto free_rest; default: - log_info(peer->log, "Unknown step type %u", step->next_case); + log_info(peer->log, "Unknown step type %u", step->nextcase); command_htlc_set_fail(peer, htlc, VERSION_NOT_SUPPORTED_505, "unknown step type"); goto free_rest; } free_rest: - tal_free(rest_of_route); + tal_free(step); } static void our_htlc_failed(struct peer *peer, struct htlc *htlc) @@ -4396,6 +4407,11 @@ static void json_newhtlc(struct command *cmd, struct htlc *htlc; const char *err; enum fail_error error_code; + struct hoppayload *hoppayloads; + u8 sessionkey[32]; + struct onionpacket *packet; + u8 *onion; + struct pubkey *path = tal_arrz(cmd, struct pubkey, 1); if (!json_get_params(buffer, params, "peerid", &peeridtok, @@ -4445,10 +4461,20 @@ static void json_newhtlc(struct command *cmd, return; } + tal_arr(cmd, struct pubkey, 1); + hoppayloads = tal_arrz(cmd, struct hoppayload, 1); + memcpy(&path[0], peer->id, sizeof(struct pubkey)); + randombytes_buf(&sessionkey, sizeof(sessionkey)); + packet = create_onionpacket( + cmd, + cmd->dstate->secpctx, + path, + hoppayloads, sessionkey, (u8*)"", 0); + onion = serialize_onionpacket(cmd, cmd->dstate->secpctx, packet); + log_debug(peer->log, "JSON command to add new HTLC"); err = command_htlc_add(peer, msatoshi, expiry, &rhash, NULL, - onion_create(cmd, cmd->dstate->secpctx, - NULL, NULL, 0), + onion, &error_code, &htlc); if (err) { command_fail(cmd, "could not add htlc: %u:%s", error_code, err); diff --git a/daemon/peer.h b/daemon/peer.h index 6a580447e..344ccd239 100644 --- a/daemon/peer.h +++ b/daemon/peer.h @@ -240,6 +240,7 @@ struct peer_address { void setup_listeners(struct lightningd_state *dstate, unsigned int portnum); struct peer *find_peer(struct lightningd_state *dstate, const struct pubkey *id); +struct peer *find_peer_by_pkhash(struct lightningd_state *dstate, const u8 *pkhash); struct peer *new_peer(struct lightningd_state *dstate, struct log *log, From d30f3f1a40ebafae7b444967f5388a883d2d9f0e Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Sun, 16 Oct 2016 16:24:28 +0200 Subject: [PATCH 3/3] sphinx: Remove obsolete onion implementation --- Makefile | 16 +- daemon/onion.c | 82 ------ daemon/onion.h | 21 -- test/onion_key.c | 102 ------- test/onion_key.h | 24 -- test/test_onion.c | 642 --------------------------------------------- test/test_onion.py | 345 ------------------------ 7 files changed, 1 insertion(+), 1231 deletions(-) delete mode 100644 daemon/onion.c delete mode 100644 daemon/onion.h delete mode 100644 test/onion_key.c delete mode 100644 test/onion_key.h delete mode 100644 test/test_onion.c delete mode 100644 test/test_onion.py diff --git a/Makefile b/Makefile index 54a360254..52455a724 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,7 @@ BITCOIN_FEATURES := \ FEATURES := $(BITCOIN_FEATURES) TEST_PROGRAMS := \ - test/onion_key \ test/test_protocol \ - test/test_onion \ test/test_sphinx BITCOIN_SRC := \ @@ -200,18 +198,6 @@ $(CCAN_OBJS) $(CDUMP_OBJS) $(HELPER_OBJS) $(BITCOIN_OBJS) $(TEST_PROGRAMS:=.o) c # Except for CCAN, everything depends on bitcoin/ and core headers. $(HELPER_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(TEST_PROGRAMS:=.o): $(BITCOIN_HEADERS) $(CORE_HEADERS) $(CCAN_HEADERS) $(GEN_HEADERS) -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 - test-protocol: test/test_protocol set -e; TMP=`mktemp`; [ -n "$(NO_VALGRIND)" ] || PREFIX="valgrind -q --error-exitcode=7"; for f in test/commits/*.script; do if ! $$PREFIX test/test_protocol < $$f > $$TMP; then echo "test/test_protocol < $$f FAILED" >&2; exit 1; fi; diff -u $$TMP $$f.expected; done; rm $$TMP @@ -220,7 +206,7 @@ doc/protocol-%.svg: test/test_protocol protocol-diagrams: $(patsubst %.script, doc/protocol-%.svg, $(notdir $(wildcard test/commits/*.script))) -check: test-onion test-protocol bitcoin-tests +check: test-protocol bitcoin-tests include bitcoin/Makefile diff --git a/daemon/onion.c b/daemon/onion.c deleted file mode 100644 index 5e6238134..000000000 --- a/daemon/onion.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "log.h" -#include "onion.h" -#include "peer.h" -#include "protobuf_convert.h" -#include "routing.h" -#include - -/* FIXME: http://www.cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf */ - -/* Frees r */ -static const u8 *to_onion(const tal_t *ctx, const Route *r) -{ - u8 *onion = tal_arr(ctx, u8, route__get_packed_size(r)); - route__pack(r, onion); - tal_free(r); - return onion; -} - -/* Create an onion for this path. */ -const u8 *onion_create(const tal_t *ctx, - secp256k1_context *secpctx, - const struct pubkey *ids, - const u64 *amounts, - size_t num_hops) -{ - Route *r = tal(ctx, Route); - size_t i; - - route__init(r); - r->n_steps = num_hops + 1; - r->steps = tal_arr(r, RouteStep *, r->n_steps); - - for (i = 0; i < num_hops; i++) { - r->steps[i] = tal(r, RouteStep); - route_step__init(r->steps[i]); - r->steps[i]->next_case = ROUTE_STEP__NEXT_BITCOIN; - r->steps[i]->bitcoin = pubkey_to_proto(r, secpctx, &ids[i]); - r->steps[i]->amount = amounts[i]; - } - - /* Now the stop marker. */ - r->steps[i] = tal(r, RouteStep); - route_step__init(r->steps[i]); - r->steps[i]->next_case = ROUTE_STEP__NEXT_END; - r->steps[i]->end = true; - r->steps[i]->amount = 0; - - return to_onion(ctx, r); -} - -/* Decode next step in the route, and fill out the onion to send onwards. */ -RouteStep *onion_unwrap(struct peer *peer, - const void *data, size_t len, const u8 **next) -{ - struct ProtobufCAllocator *prototal = make_prototal(peer); - Route *r; - RouteStep *step; - - r = route__unpack(prototal, len, data); - if (!r || r->n_steps == 0) { - log_unusual(peer->log, "Failed to unwrap onion"); - tal_free(prototal); - return NULL; - } - - /* Remove first step. */ - step = r->steps[0]; - /* Make sure that step owns the rest */ - steal_from_prototal(peer, prototal, step); - - /* Re-pack with remaining steps. */ - r->n_steps--; - memmove(r->steps, r->steps + 1, sizeof(*r->steps) * r->n_steps); - - if (!r->n_steps) { - *next = NULL; - tal_free(r); - } else - *next = to_onion(peer, r); - - return step; -} diff --git a/daemon/onion.h b/daemon/onion.h deleted file mode 100644 index 90405524e..000000000 --- a/daemon/onion.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef LIGHTNING_DAEMON_ONION_H -#define LIGHTNING_DAEMON_ONION_H -#include "config.h" -#include "lightning.pb-c.h" -#include -#include - -struct peer; -struct node_connection; - -/* Decode next step in the route, and fill out the onion to send onwards. */ -RouteStep *onion_unwrap(struct peer *peer, - const void *data, size_t len, const u8 **next); - -/* Create an onion for sending msatoshi down path, paying fees. */ -const u8 *onion_create(const tal_t *ctx, - secp256k1_context *secpctx, - const struct pubkey *ids, - const u64 *amounts, - size_t num_hops); -#endif /* LIGHTNING_DAEMON_ONION_H */ diff --git a/test/onion_key.c b/test/onion_key.c deleted file mode 100644 index 611ee27bc..000000000 --- a/test/onion_key.c +++ /dev/null @@ -1,102 +0,0 @@ -#define _GNU_SOURCE 1 -#include "onion_key.h" -#include "version.h" -#include -#include -#include -#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; -} - -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 compressed_pubkey *pubkey) -{ - secp256k1_pubkey pkey; - size_t len = sizeof(pubkey->u8); - - random_key(ctx, seckey, &pkey); - - secp256k1_ec_pubkey_serialize(ctx, pubkey->u8, &len, &pkey, - SECP256K1_EC_COMPRESSED); - assert(len == sizeof(pubkey->u8)); -} - -static void print_keypair(bool pub, bool priv) -{ - secp256k1_context *ctx; - struct seckey seckey; - struct compressed_pubkey pubkey; - char sechex[hex_str_size(sizeof(seckey))]; - char pubhex[hex_str_size(sizeof(pubkey))]; - - 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)); - - if (pub && priv) { - printf("%s:%s\n", sechex, pubhex); - } else { - printf("%s\n", (priv ? sechex : pubhex)); - } -} - -int main(int argc, char *argv[]) -{ - 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 { - int i; - for (i = 1; i < argc; i++) { - srandom(atoi(argv[i])); - print_keypair(pub, priv); - } - } - - return 0; -} diff --git a/test/onion_key.h b/test/onion_key.h deleted file mode 100644 index 61cd36083..000000000 --- a/test/onion_key.h +++ /dev/null @@ -1,24 +0,0 @@ -#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; -}; - -/* 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]; -}; - -#endif /* ONION_KEY_H */ diff --git a/test/test_onion.c b/test/test_onion.c deleted file mode 100644 index 33c4a4162..000000000 --- a/test/test_onion.c +++ /dev/null @@ -1,642 +0,0 @@ -#define _GNU_SOURCE 1 -#include "onion_key.h" -#include "version.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * The client knows the server's public key S (which has corresponding - private key s) in advance. - * The client generates an ephemeral private key r, and its corresponding - public key R. - * The client computes K = ECDH(r, S), and sends R to the server at - connection establishing time. - * The server receives R, and computes K = ECHD(R, s). - * Both client and server compute Kenc = SHA256(K || 0) and Kmac = SHA256(K - || 1), and now send HMAC-SHA256(key=Kmac, msg=AES(key=Kenc, msg=m)) instead - of m, for each message. -*/ - -struct enckey { - struct sha256 k; -}; - -struct hmackey { - struct sha256 k; -}; - -struct iv { - unsigned char iv[crypto_stream_aes128ctr_NONCEBYTES]; -}; - -static void sha_with_seed(const unsigned char secret[32], - unsigned char seed, - struct sha256 *res) -{ - struct sha256_ctx ctx; - - sha256_init(&ctx); - sha256_update(&ctx, memcheck(secret, 32), 32); - sha256_u8(&ctx, seed); - sha256_done(&ctx, res); -} - -static struct enckey enckey_from_secret(const unsigned char secret[32]) -{ - struct enckey enckey; - sha_with_seed(secret, 0, &enckey.k); - return enckey; -} - -static struct hmackey hmackey_from_secret(const unsigned char secret[32]) -{ - struct hmackey hmackey; - sha_with_seed(secret, 1, &hmackey.k); - memcheck(&hmackey, 1); - return hmackey; -} - - -static void ivs_from_secret(const unsigned char secret[32], - struct iv *iv, struct iv *pad_iv) -{ - struct sha256 sha; - sha_with_seed(secret, 2, &sha); - 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! */ -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 = sizeof(tmp); - - 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)); -} - -/* - * Onion routing: - * - * Each step decrypts the payload, and removes its message. It then - * pads at the end to keep constant size, by encrypting 0 bytes (ZPAD) - * - * You can see the result of the unwrapping here: - * - * ENC1(PKT1 ENC2(PKT2 ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD))))) - * After 1: ENC2(PKT2 ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD)))) - * ENC1(ZPAD) - * After 2: ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD))) - * DEC2(ENC1(ZPAD)) - * ENC2(ZPAD) - * After 3: ENC4(PKT4 ENC5(PKT5 RPAD))) - * DEC3(DEC2(ENC1(ZPAD)) ENC2(ZPAD)) - * ENC3(ZPAD) - * After 4: ENC5(PKT5 RPAD) - * DEC4(DEC3(DEC2(ENC1(ZPAD)) ENC2(ZPAD)) ENC3(ZPAD)) - * ENC4(ZPAD) - * - * ENC1(PKT1 ENC2(PKT2)) - * => ENC2(PKT2) ENC1(ZPAD) - * => PKT2 DEC2(ENC1(ZPAD)) - */ -#define MESSAGE_SIZE 128 -#define MAX_HOPS 20 - -struct hop { - unsigned char msg[MESSAGE_SIZE]; - struct onion_pubkey pubkey; - 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) -{ - return crypto_stream_aes128ctr_xor(dst, src, len, iv->iv, enckey->k.u.u8) == 0; -} - -static bool aes_decrypt(void *dst, const void *src, size_t len, - const struct enckey *enckey, const struct iv *iv) -{ - return crypto_stream_aes128ctr_xor(dst, src, len, iv->iv, enckey->k.u.u8) == 0; -} - -#if 0 -static void dump_contents(const void *data, size_t n) -{ - size_t i; - const unsigned char *p = memcheck(data, n); - - for (i = 0; i < n; i++) { - printf("%02x", p[i]); - if (i % 16 == 15) - printf("\n"); - } -} -#endif - -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 encrypting junk before the actual data. - */ - char tmp[offset + len]; - - /* Keep valgrind happy. */ - memset(tmp, 0, offset); - memcpy(tmp + offset, src, len); - - /* FIXME: Assumes we are allowed to encrypt in place! */ - if (!aes_encrypt(tmp, tmp, offset+len, enckey, iv)) - return false; - - memcpy(dst, tmp + offset, len); - return true; -} - -/* Padding is created by encrypting zeroes. */ -static void add_padding(struct hop *padding, - const struct enckey *enckey, - const struct iv *pad_iv) -{ - static struct hop zerohop; - - aes_encrypt(padding, &zerohop, sizeof(zerohop), enckey, pad_iv); -} - -static void make_hmac(const struct hop *hops, size_t num_hops, - const struct hop *padding, - const struct hmackey *hmackey, - struct sha256 *hmac) -{ - crypto_auth_hmacsha256_state state; - size_t len, padlen = (MAX_HOPS - num_hops) * sizeof(struct hop); - len = num_hops*sizeof(struct hop) - sizeof(hops->hmac); - crypto_auth_hmacsha256_init(&state, hmackey->k.u.u8, sizeof(hmackey->k)); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)padding, padlen), padlen); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)hops, len), len); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)padding, padlen), padlen); - crypto_auth_hmacsha256_final(&state, hmac->u.u8); -} - -#if 0 -static 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)) - -static void dump_pkey(secp256k1_context *ctx, secp256k1_pubkey pkey) { - unsigned char tmp[65]; - size_t len = sizeof(tmp); - secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, 0); - dump_hex(tmp); -} -#endif - -static bool check_hmac(struct onion *onion, const struct hmackey *hmackey) -{ - struct sha256 hmac; - - make_hmac(onion->hop, MAX_HOPS, NULL, hmackey, &hmac); - return sodium_memcmp(&hmac, &myhop(onion)->hmac, sizeof(hmac)) == 0; -} - -static bool create_onion(const secp256k1_pubkey pubkey[], - char *const msg[], - size_t num, - struct onion *onion) -{ - int i; - 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]; - crypto_auth_hmacsha256_state padding_hmac[MAX_HOPS]; - struct hop padding[MAX_HOPS]; - size_t junk_hops; - secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - bool ok = false; - - if (num > MAX_HOPS) - goto fail; - - /* 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]; - - gen_keys(ctx, &seckeys[i], &pubkeys[i]); - - - /* Make shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &pubkey[i], seckeys[i].u.u8)) - goto fail; - - hmackeys[i] = hmackey_from_secret(memcheck(secret, 32)); - enckeys[i] = enckey_from_secret(secret); - ivs_from_secret(secret, &ivs[i], &pad_ivs[i]); - } - - /* - * Building the onion is a little tricky. - * - * First, there is the padding. That's generated by previous nodes, - * and "decrypted" by the others. So we have to generate that - * forwards. - */ - 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]); - crypto_auth_hmacsha256_init(&padding_hmac[i], - hmackeys[i].k.u.u8, - sizeof(hmackeys[i].k)); - crypto_auth_hmacsha256_update(&padding_hmac[i], - memcheck((unsigned char *)padding, - i * sizeof(struct hop)), - i * sizeof(struct hop)); - } - - /* - * Now the normal onion is generated backwards. - */ - - /* Unused hops filled with random, so even recipient can't tell - * how many were used. */ - junk_hops = MAX_HOPS - num; - random_bytes(onion->hop, 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; - - /* Our entry is at tail of onion. */ - myhop = onion->hop + other_hops; - - /* Now populate our hop. */ - myhop->pubkey = pubkeys[i]; - /* Set message. */ - assert(strlen(msg[i]) < MESSAGE_SIZE); - 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), - onion, onion, - other_hops * sizeof(struct hop) - + sizeof(myhop->msg), - &enckeys[i], &ivs[i])) - goto fail; - - /* HMAC covers entire thing except hmac itself. */ - len = (other_hops + 1)*sizeof(struct hop) - sizeof(myhop->hmac); - crypto_auth_hmacsha256_update(&padding_hmac[i], - memcheck((unsigned char *)onion, len), len); - crypto_auth_hmacsha256_final(&padding_hmac[i], myhop->hmac.u.u8); - } - - ok = true; -fail: - secp256k1_context_destroy(ctx); - return ok; -} - -static bool pubkey_parse(const secp256k1_context *ctx, - secp256k1_pubkey* pubkey, - struct onion_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. - * - * Returns enckey and pad_iv for use in unwrap. - */ -static bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, - struct enckey *enckey, struct iv *pad_iv) -{ - secp256k1_context *ctx; - 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, &pubkey, myseckey->u.u8)) - goto fail; - - hmackey = hmackey_from_secret(secret); - *enckey = enckey_from_secret(secret); - ivs_from_secret(secret, &iv, pad_iv); - - /* Check HMAC. */ -#if 0 - printf("Checking HMAC using key%02x%02x%02x%02x%02x%02x%02x%02x (offset %u len %zu) for %02x%02x%02x%02x%02x%02x%02x%02x...%02x%02x%02x\n", - hmackey.k[0], hmackey.k[1], - hmackey.k[2], hmackey.k[3], - hmackey.k[4], hmackey.k[5], - hmackey.k[6], hmackey.k[7], - SHA256_DIGEST_LENGTH, - sizeof(*onion) - SHA256_DIGEST_LENGTH, - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[0], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[1], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[2], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[3], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[4], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[5], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[6], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[7], - ((unsigned char *)(onion + 1))[-3], - ((unsigned char *)(onion + 1))[-2], - ((unsigned char *)(onion + 1))[-1]); - dump_contents((unsigned char *)onion + SHA256_DIGEST_LENGTH, - sizeof(*onion) - SHA256_DIGEST_LENGTH); -#endif - if (!check_hmac(onion, &hmackey)) - goto fail; - - /* 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; - - secp256k1_context_destroy(ctx); - return true; - -fail: - secp256k1_context_destroy(ctx); - return false; -} - -/* Get next layer of onion, for forwarding. */ -static bool peel_onion(struct onion *onion, - const struct enckey *enckey, const struct iv *pad_iv) -{ - /* 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[0], 0, sizeof(onion->hop[0])); - return aes_encrypt(&onion->hop[0], &onion->hop[0], - 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] = { }; - - if (!hex_decode(arg, strlen(arg), tmp, sizeof(tmp))) - return false; - - return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); -} - -static char *make_message(secp256k1_context *ctx, - const secp256k1_pubkey *pubkey) -{ - char *m; - unsigned char tmp[33]; - size_t len = sizeof(tmp); - char hexstr[hex_str_size(20)]; - - 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; -} - -int main(int argc, char *argv[]) -{ - secp256k1_context *ctx; - struct onion onion; - bool generate = false, decode = false; - - 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); - - 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(ctx, &pubkeys[i-1]); - } - - 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; - - 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 (!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(ctx, &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 '%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; -} diff --git a/test/test_onion.py b/test/test_onion.py deleted file mode 100644 index a09983fac..000000000 --- a/test/test_onion.py +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env python - -import argparse -import sys -import time - -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() - - - - - - -## 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 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: - 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) - -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 - 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') - ivs = cls.tweak_sha(sec, b'\x02') - iv, pad_iv = ivs[:16], ivs[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 - - 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 decrypt(self): - pad = self.enc_pad(self.enckey, self.pad_iv) - - 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) # 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 = 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 - -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 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__": - main(sys.argv[1:]) - sys.exit(0) -