Merge remote-tracking branch 'origin/pr/46'

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2016-10-17 12:37:16 +10:30
commit 02cb651748
14 changed files with 854 additions and 1271 deletions

View File

@ -23,9 +23,8 @@ BITCOIN_FEATURES := \
FEATURES := $(BITCOIN_FEATURES)
TEST_PROGRAMS := \
test/onion_key \
test/test_protocol \
test/test_onion
test/test_sphinx
BITCOIN_SRC := \
bitcoin/base58.c \
@ -38,6 +37,7 @@ BITCOIN_SRC := \
bitcoin/signature.c \
bitcoin/tx.c \
bitcoin/varint.c
BITCOIN_OBJS := $(BITCOIN_SRC:.c=.o)
CORE_SRC := \
@ -198,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
@ -218,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

View File

@ -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

View File

@ -1,82 +0,0 @@
#include "log.h"
#include "onion.h"
#include "peer.h"
#include "protobuf_convert.h"
#include "routing.h"
#include <string.h>
/* 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;
}

View File

@ -1,21 +0,0 @@
#ifndef LIGHTNING_DAEMON_ONION_H
#define LIGHTNING_DAEMON_ONION_H
#include "config.h"
#include "lightning.pb-c.h"
#include <ccan/short_types/short_types.h>
#include <secp256k1.h>
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 */

View File

@ -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 <ccan/str/hex/hex.h>
#include <ccan/structeq/structeq.h>
#include <inttypes.h>
#include <sodium/randombytes.h>
/* 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);

View File

@ -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 <inttypes.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sodium/randombytes.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
@ -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)
@ -4420,6 +4431,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,
@ -4469,10 +4485,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);

View File

@ -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,

526
daemon/sphinx.c Normal file
View File

@ -0,0 +1,526 @@
#include "sphinx.h"
#include <assert.h>
#include <ccan/crypto/ripemd160/ripemd160.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/mem/mem.h>
#include <err.h>
#include <sodium/crypto_auth_hmacsha256.h>
#include <sodium/crypto_stream_chacha20.h>
#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, &params[0].ephemeralkey, sessionkey) != 1)
return NULL;
if (!create_shared_secret(
secpctx, params[0].secret, &path[0].pubkey, sessionkey))
return NULL;
compute_blinding_factor(
secpctx, &params[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, &params[i].ephemeralkey,
&params[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(&params[i].secret, &h, sizeof(h));
compute_blinding_factor(
secpctx, &params[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, &params[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;
}

126
daemon/sphinx.h Normal file
View File

@ -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 <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <secp256k1.h>
#include <sodium/randombytes.h>
#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 */

View File

@ -1,102 +0,0 @@
#define _GNU_SOURCE 1
#include "onion_key.h"
#include "version.h"
#include <time.h>
#include <ccan/str/hex/hex.h>
#include <ccan/opt/opt.h>
#include <assert.h>
#include <secp256k1.h>
#include <secp256k1_ecdh.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
/* 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,
"[<seeds>...]\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;
}

View File

@ -1,24 +0,0 @@
#ifndef ONION_KEY_H
#define ONION_KEY_H
#include <ccan/endian/endian.h>
#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 */

View File

@ -1,642 +0,0 @@
#define _GNU_SOURCE 1
#include "onion_key.h"
#include "version.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <stdbool.h>
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/tal/tal.h>
#include <ccan/mem/mem.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/endian/endian.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/opt/opt.h>
#include <ccan/str/hex/hex.h>
#include <secp256k1.h>
#include <secp256k1_ecdh.h>
#include <sodium/crypto_stream_aes128ctr.h>
#include <sodium/crypto_auth_hmacsha256.h>
#include <sodium/utils.h>
/*
* 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 <pubkey>... OR\n"
"--decode <privkey>\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;
}

View File

@ -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)

106
test/test_sphinx.c Normal file
View File

@ -0,0 +1,106 @@
#include <secp256k1.h>
#include <ccan/opt/opt.h>
#include <ccan/short_types/short_types.h>
#include <string.h>
#include <ccan/str/hex/hex.h>
#include <ccan/read_write_all/read_write_all.h>
#include <err.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#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 <pubkey1> <pubkey2>... OR\n"
"--decode <privkey>\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; i<num_hops; i++)
memset(&hoppayloads[i], 'A', sizeof(hoppayloads[i]));
struct onionpacket *res = create_onionpacket(ctx, secpctx,
path,
hoppayloads,
sessionkey,
(u8*)"testing",
7);
u8 *serialized = serialize_onionpacket(ctx, secpctx, res);
if (!serialized)
errx(1, "Error serializing message.");
char hextemp[2 * tal_count(serialized) + 1];
hex_encode(serialized, tal_count(serialized), hextemp, sizeof(hextemp));
printf("%s\n", hextemp);
} else if (decode) {
struct route_step *step;
struct onionpacket *msg;
struct privkey seckey;
const tal_t *ctx = talz(NULL, tal_t);
u8 serialized[TOTAL_PACKET_SIZE];
char hextemp[2 * sizeof(serialized) + 1];
memset(hextemp, 0, sizeof(hextemp));
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 (!read_all(STDIN_FILENO, hextemp, sizeof(hextemp)))
errx(1, "Reading in onion");
hex_decode(hextemp, sizeof(hextemp), serialized, sizeof(serialized));
msg = parse_onionpacket(ctx, secpctx, serialized, sizeof(serialized));
if (!msg)
errx(1, "Error parsing message.");
step = process_onionpacket(ctx, secpctx, msg, &seckey);
if (!step->next)
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;
}