Skip to content

Commit

Permalink
sphinx: add (unused) infrastructure for end-to-end payload.
Browse files Browse the repository at this point in the history
The Sphinx paper calls this "payload", but we've already used that
liberally in "per-hop payload":

    The payload of the message is kept separate from the mix header
    used to perform the routing. It is decrypted at each stage of
    mixing using a block cipher with a large block size (the size of
    the entire message), such as LIONESS [1]. In case the adversary
    modifies the payload in transit, any information contained in it
    becomes irrecoverable. Sender-anonymous messages contain the final
    address of the message, as well as the message itself as part of
    the payload, and so any modification destroys this information.

Since we don't want to add a block cypher, we use chacha20poly1305
with the shared secret as the key instead.

This is described in a BOLT proposal:

	lightning/bolts#755

Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Mar 6, 2020
1 parent 1267d97 commit 79ed54f
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
91 changes: 91 additions & 0 deletions common/sphinx.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <assert.h>

#include <ccan/array_size/array_size.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/cast/cast.h>
#include <ccan/crypto/ripemd160/ripemd160.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/mem/mem.h>
Expand All @@ -14,6 +16,7 @@

#include <secp256k1_ecdh.h>

#include <sodium/crypto_aead_chacha20poly1305.h>
#include <sodium/crypto_auth_hmacsha256.h>
#include <sodium/crypto_stream_chacha20.h>

Expand Down Expand Up @@ -550,6 +553,94 @@ struct onionpacket *create_onionpacket(
return packet;
}

u8 *create_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *secret)
{
u8 *enc;

/* Sphinx paper uses a block cypher here, with the first
* bytes being the receivers' key. We use AD instead,
* since we already rely on that. */
static unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
unsigned long long clen;
u8 key[crypto_aead_chacha20poly1305_IETF_KEYBYTES];
int ret;
size_t e2e_len = tal_bytelen(e2e_payload);

BUILD_ASSERT(sizeof(key) == KEY_LEN);

/* If taken, reuse in-place */
if (taken(e2e_payload)) {
tal_resize(&e2e_payload,
e2e_len + crypto_aead_chacha20poly1305_IETF_ABYTES);
enc = cast_const(u8 *, e2e_payload);
} else {
enc = tal_arr(ctx, u8,
e2e_len + crypto_aead_chacha20poly1305_IETF_ABYTES);
}

generate_key(key, "pi", 2, secret);

ret = crypto_aead_chacha20poly1305_ietf_encrypt(enc, &clen,
e2e_payload,
e2e_len,
NULL, 0,
NULL, npub, key);
assert(ret == 0);
assert(clen == tal_bytelen(enc));

return enc;
}

u8 *unwrap_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *shared_secret)
{
u8 key[KEY_LEN];
u8 *dec = tal_dup_talarr(ctx, u8, e2e_payload);

generate_key(key, "pi", 2, shared_secret);
xor_cipher_stream(dec, key, tal_bytelen(dec));
return dec;
}

u8 *final_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *shared_secret)
{
/* Final hop needs to check decryption */
u8 key[KEY_LEN];
unsigned long long dlen;
static unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
u8 *ret;

if (tal_bytelen(e2e_payload) < crypto_aead_chacha20poly1305_IETF_ABYTES) {
if (taken(e2e_payload))
tal_free(e2e_payload);
return NULL;
}

if (taken(e2e_payload))
ret = cast_const(u8 *, tal_steal(ctx, e2e_payload));
else
ret = tal_arr(ctx, u8, tal_bytelen(e2e_payload) - crypto_aead_chacha20poly1305_IETF_ABYTES);

generate_key(key, "pi", 2, shared_secret);
if (crypto_aead_chacha20poly1305_ietf_decrypt(ret, &dlen,
NULL,
e2e_payload,
tal_bytelen(e2e_payload),
NULL, 0,
npub, key) != 0)
return tal_free(ret);

/* Trim to length if we are reusing buffer. */
if (ret == e2e_payload)
tal_resize(&ret, dlen);
return ret;
}

#if DEVELOPER
bool dev_fail_process_onionpacket;
#endif
Expand Down
40 changes: 40 additions & 0 deletions common/sphinx.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,46 @@ u8 *unwrap_onionreply(const tal_t *ctx,
const struct onionreply *reply,
int *origin_index);

/**
* create_e2e_payload - Encrypt end-to-end payload for onion.
*
* @ctx: tal context to allocate from
* @e2e_payload: tal_bytelen(@e2e_payload) of payload (copied unless taken())
* @shared_secrets: shared secret for recipient
*/
u8 *create_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *shared_secret);

/**
* unwrap_e2e_payload - process an incoming end-to-end-paylod by stripping one
*
* @ctx: tal context to allocate from
* @e2e_payload: tal_bytelen(@e2e_payload) of payload (copied unless taken())
* @shared_secret: the result of onion_shared_secret.
*
* Never fails. Note that this is *not* to be used for the final
* destination: see final_e2e_payload.
*/
u8 *unwrap_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *shared_secret);

/* This is perfectly symmetrical */
#define wrap_e2e_payload unwrap_e2e_payload

/**
* final_e2e_payload - process a final end-to-end payload
*
* @ctx: tal context to allocate from
* @e2e_payload: tal_bytelen(@e2e_payload) of payload (copied unless taken())
* @shared_secret: the result of onion_shared_secret.
*
* Returns NULL if it was corrupt */
u8 *final_e2e_payload(const tal_t *ctx,
const u8 *e2e_payload TAKES,
const struct secret *shared_secret);

/**
* Create a new empty sphinx_path.
*
Expand Down

0 comments on commit 79ed54f

Please sign in to comment.